home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 November / PCWNOV08.iso / Software / Freeware / NoScript 1.7.7 / noscript-1.7.7-fx+mz+sm.xpi / components / noscriptService.js
Encoding:
Text File  |  2008-07-14  |  190.8 KB  |  5,827 lines

  1. /***** BEGIN LICENSE BLOCK *****
  2.  
  3. NoScript - a Firefox extension for whitelist driven safe JavaScript execution
  4. Copyright (C) 2004-2008 Giorgio Maone - g.maone@informaction.com
  5.  
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with this program; if not, write to the Free Software
  18. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19.  
  20. ***** END LICENSE BLOCK *****/
  21.  
  22. // Scaffolding
  23.  
  24. const CI = Components.interfaces;
  25. const CC = Components.classes;
  26. const STATE_START = CI.nsIWebProgressListener.STATE_START;
  27. const STATE_DOC = CI.nsIWebProgressListener.STATE_IS_DOCUMENT;
  28. const STATE_START_DOC = STATE_START | STATE_DOC
  29. const NS_BINDING_ABORTED = 0x804B0002;
  30. const CP_OK = 1;
  31. const CP_NOP = function() { return CP_OK };
  32.  
  33. const LOG_CONTENT_BLOCK = 1;
  34. const LOG_CONTENT_CALL = 2;
  35. const LOG_CONTENT_INTERCEPT = 4;
  36. const LOG_CHROME_WIN = 8;
  37. const LOG_XSS_FILTER = 16;
  38. const LOG_INJECTION_CHECK = 32;
  39. const LOG_DOMUTILS = 64;
  40. const LOG_JS = 128;
  41. const LOG_LEAKS = 1024;
  42. const LOG_SNIFF = 2048;
  43.  
  44. // component defined in this file
  45. const EXTENSION_ID="{73a6fe31-595d-460b-a920-fcc0f8843232}";
  46. const SERVICE_NAME="NoScript Service";
  47. const SERVICE_ID="{31aec909-8e86-4397-9380-63a59e0c5ff5}";
  48. const SERVICE_CTRID = "@maone.net/noscript-service;1";
  49. const SERVICE_CONSTRUCTOR=NoscriptService;
  50.  
  51. const SERVICE_CID = Components.ID(SERVICE_ID);
  52.  
  53. // interfaces implemented by this component
  54. const SERVICE_IIDS = 
  55. CI.nsIObserver,
  56. CI.nsISupports,
  57. CI.nsISupportsWeakReference,
  58. CI.nsIContentPolicy,
  59. CI.nsIWebProgressListener,
  60. CI.nsIChannelEventSink
  61. ];
  62.  
  63. // categories which this component is registered in
  64. const SERVICE_CATS = ["app-startup", "content-policy"];
  65.  
  66.  
  67. // Factory object
  68. const SERVICE_FACTORY = {
  69.   _instance: null,
  70.   createInstance: function (outer, iid) {
  71.     if (outer != null)
  72.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  73.  
  74.     xpcom_checkInterfaces(iid, SERVICE_IIDS, Components.results.NS_ERROR_INVALID_ARG);
  75.     // kept this for flexibility sake, but we're really adopting an
  76.     // early instantiation and late init singleton pattern
  77.     return this._instance==null ? this._instance=new SERVICE_CONSTRUCTOR() : this._instance;
  78.   }
  79. };
  80.  
  81. function xpcom_generateQI(iids) {
  82.   var lines = [];
  83.   for (var j = iids.length; j-- > 0;) {
  84.     lines.push("if(CI." + iids[j].name + ".equals(iid)) return this;");
  85.   }
  86.   lines.push("throw Components.results.NS_ERROR_NO_INTERFACE;");
  87.   return new Function("iid", lines.join("\n"));
  88. }
  89.  
  90.  
  91. function xpcom_checkInterfaces(iid,iids,ex) {
  92.   for (var j = iids.length; j-- >0;) {
  93.     if (iid.equals(iids[j])) return true;
  94.   }
  95.   throw ex;
  96. }
  97.  
  98. var Module = {
  99.   firstTime: true,
  100.   registerSelf: function(compMgr, fileSpec, location, type) {
  101.     if (this.firstTime) {
  102.       compMgr.QueryInterface(CI.nsIComponentRegistrar
  103.         ).registerFactoryLocation(SERVICE_CID,
  104.         SERVICE_NAME,
  105.         SERVICE_CTRID, 
  106.         fileSpec,
  107.         location, 
  108.         type);
  109.       const catman = CC['@mozilla.org/categorymanager;1'
  110.         ].getService(CI.nsICategoryManager);
  111.       for (var j=0, len = SERVICE_CATS.length; j < len; j++) {
  112.         catman.addCategoryEntry(SERVICE_CATS[j],
  113.           SERVICE_CTRID, SERVICE_CTRID, true, true);
  114.       }
  115.       this.firstTime = false;
  116.     }
  117.   },
  118.   
  119.   unregisterSelf: function(compMgr, fileSpec, location) {
  120.     compMgr.QueryInterface(CI.nsIComponentRegistrar
  121.       ).unregisterFactoryLocation(SERVICE_CID, fileSpec);
  122.     const catman = CC['@mozilla.org/categorymanager;1'
  123.         ].getService(CI.nsICategoryManager);
  124.     for (var j = 0, len=SERVICE_CATS.length; j<len; j++) {
  125.       catman.deleteCategoryEntry(SERVICE_CATS[j], SERVICE_CTRID, true);
  126.     }
  127.   },
  128.  
  129.   getClassObject: function (compMgr, cid, iid) {
  130.     if (cid.equals(SERVICE_CID))
  131.       return SERVICE_FACTORY;
  132.   
  133.     if (!iid.equals(CI.nsIFactory))
  134.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  135.     
  136.     throw Components.results.NS_ERROR_NO_INTERFACE;
  137.   },
  138.  
  139.   canUnload: function(compMgr) {
  140.     return true;
  141.   }
  142. }
  143. function NSGetModule(compMgr, fileSpec) {
  144.   return Module;
  145. }
  146.  
  147. // END XPCOM Scaffolding
  148.  
  149. const URIValidator = {
  150.   validators: null,
  151.   QueryInterface: xpcom_generateQI([CI.nsIObserver, CI.nsISupportsWeakReference, CI.nsISupports]),
  152.   
  153.   // returns false if absolute URI is not valid, undefined if it cannot be validated (i.e. no validator is found for this scheme) 
  154.   validate: function(uriSpec) {
  155.     if (!uriSpec) return false;
  156.     var parts = uriSpec.split(":");
  157.     if (parts.length < 2) return false;
  158.     var scheme = parts.shift().toLowerCase();
  159.     if (!scheme) return false;
  160.     if (!this.validators) this.init();
  161.     var validator = this.validators[scheme];
  162.     try {
  163.       // using unescape rather than decodeURI for a reason:
  164.       // many external URL (e.g. mailto) default to ISO8859, and we would fail,
  165.       // but on the other hand rules marking as invalid non-null high unicode chars are unlikely (let's hope it) 
  166.       return validator && validator.test(unescape(parts.join(":"))); 
  167.     } catch(e) {
  168.       return false;
  169.     }
  170.   },
  171.   
  172.   init: function() {
  173.     this.validators = {};
  174.     var prefs = CC["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefService)
  175.       .getBranch("noscript.urivalid.").QueryInterface(CI.nsIPrefBranch2);
  176.     for each(var key in prefs.getChildList("", {})) {
  177.       this.parseValidator(prefs, key);
  178.     }
  179.     prefs.addObserver("", this, true);
  180.   },
  181.   parseValidator: function(prefs, key) {
  182.     try {
  183.       this.validators[key] = new RegExp("^" + prefs.getCharPref(key) + "$");
  184.     } catch(e) {
  185.       delete this.validators[key];
  186.     }
  187.   },
  188.   observe: function(prefs, topic, key) {
  189.     this.parseValidator(prefs, key);
  190.   }
  191. };
  192.  
  193. function Strings(chromeName) {
  194.   this.chromeName = chromeName;
  195. }
  196.  
  197. Strings.wrap = function(s, length, sep) {
  198.   if (!sep) sep = ' ';
  199.     
  200.   function wrapPara(p) {
  201.     if (!length) length = 80;
  202.     if (p.length <= length) return p;
  203.     chunks = [];
  204.     var pos;
  205.     while (p.length > length) {
  206.       pos = p.lastIndexOf(sep, length);
  207.       if (pos < 0) pos = p.indexOf(sep, length);
  208.       if (pos < 0) break;
  209.       chunks.push(p.substring(0, pos));
  210.       p = p.substring(pos + 1);
  211.     }
  212.  
  213.     if (chunks.length) {
  214.       res  = chunks.join("\n");
  215.       if (p.length) res += "\n" + p;
  216.       return res;
  217.     } else return p;
  218.   }
  219.   if (typeof(s) != "string") s = s.toString();
  220.   var paras = s.split("\n");
  221.   
  222.   for (var j = 0; j < paras.length; j++) paras[j] = wrapPara(paras[j]);
  223.   return paras.join("\n");
  224. }
  225.  
  226. Strings.prototype = {
  227.   bundles: {},
  228.   getBundle: function(path) {
  229.     if (path in this.bundles) return this.bundles[path];
  230.     try {
  231.       return this.bundles[path] = 
  232.         CC["@mozilla.org/intl/stringbundle;1"]
  233.                   .getService(CI.nsIStringBundleService)
  234.                   .createBundle("chrome://" + this.chromeName +  "/" + path +
  235.                                 "/" + this.chromeName + ".properties");
  236.     } catch(ex) {
  237.       return this.bundles[path] = null;
  238.     }
  239.   },
  240.   
  241.  
  242.   _stringFrom: function(bundle, name, parms) {
  243.     try {
  244.       return parms ? bundle.formatStringFromName(name, parms, parms.length) : bundle.GetStringFromName(name);
  245.     } catch(ex) {
  246.       return null;
  247.     }
  248.   }
  249. ,
  250.   getString: function(name, parms) {
  251.     var s=this._stringFrom(this.getBundle("locale"), name, parms);
  252.     return s || this._stringFrom(this.getBundle("content/en-US"), name, parms) || name;
  253.   }
  254.   
  255. }
  256.  
  257. const noscriptStrings = new Strings("noscript");
  258.  
  259. const SiteUtils = new function() {
  260.   const _domainPattern = /^[\w\u0080-\uffff][\w\-\.\u0080-\uffff]*$/;
  261.   
  262.   const _ios = this.ios = CC["@mozilla.org/network/io-service;1"]
  263.     .getService(CI.nsIIOService);
  264.   
  265.   const _uriFixup = this.uriFixup = CC["@mozilla.org/docshell/urifixup;1"]
  266.     .getService(CI.nsIURIFixup);
  267.   
  268.   function sorter(a, b) {
  269.     if (a == b) return 0;
  270.     if (!a) return 1;
  271.     if (!b) return -1;
  272.     const dp = _domainPattern;
  273.     return dp.test(a) ?
  274.         (dp.test(b) ? (a < b ? -1 : 1) : -1)
  275.       : (dp.test(b) ? 1 : a < b ? -1 : 1);
  276.   }
  277.   
  278.   this.sort = function(ss) {
  279.     return ss.sort(sorter);
  280.   };
  281.   
  282.   this.getSite = function(url) {
  283.     if (!url || 
  284.         url.charCodeAt(0) < 33  && // needs trimming
  285.         !(url = url.replace(/^\s*(.*?)\s*$/, '$1'))) {
  286.       return "";
  287.     }
  288.     
  289.     if (url.indexOf(":") == -1) {
  290.       return this.domainMatch(url);
  291.     }
  292.     
  293.     var scheme;
  294.     try {
  295.       scheme = this.ios.extractScheme(url).toLowerCase();
  296.       switch (scheme) {
  297.         case "http": case "https": // commonest case first
  298.           break;
  299.         case "javascript": case "data": 
  300.           return "";
  301.         case "about":
  302.           return /about:neterror(\?|$)/.test(url) ? "about:neterror" : url;
  303.         case "chrome":
  304.           return "chrome:";
  305.       }
  306.       scheme += ":";
  307.       if (url == scheme) return url;
  308.     } catch(ex) {
  309.       return this.domainMatch(url);
  310.     }
  311.     try {
  312.       // let's unwrap JAR uris
  313.       var uri = _uriFixup.createExposableURI(_ios.newURI(url, null, null));
  314.       if (uri instanceof CI.nsIJARURI) {
  315.         uri = uri.JARFile;
  316.         return uri ? this.getSite(uri.spec) : scheme;
  317.       }
  318.       try  {
  319.         return scheme + "//" + uri.hostPort;
  320.       } catch(exNoHostPort) {
  321.         var host = uri.spec.substring(scheme.length);
  322.         return /^\/\/[^\/]/.test(host) && (host = this.domainMatch(host.replace(/^\/\/([^\/]+).*/, "$1")))
  323.           ? scheme + "//" + host
  324.           : scheme;
  325.       }
  326.     } catch(ex) {
  327.       return "";
  328.     }
  329.   };
  330.   
  331.   this.list2set = function(sl) {
  332.     // kill duplicates
  333.     var prevSite = "";
  334.     var site;
  335.     for (var j = sl.length; j--> 0;) {
  336.       site = sl[j];
  337.       if ((!site) || site == prevSite) { 
  338.         sl.splice(j, 1);
  339.       } else {
  340.         prevSite = site;
  341.       }
  342.     }
  343.     return sl;
  344.   };
  345.   
  346.   this.sortedSet = function(sl) {
  347.     return this.list2set(this.sort(sl));
  348.   }
  349.   
  350.   this.splitString = function(s) {
  351.     return s && /[^\s,]/.test(s) && s.split(/\s*[,\s]\s*/) || [];
  352.   };
  353.   
  354.   this.domainMatch = function(url) {
  355.      const m = url.match(_domainPattern);
  356.      return m ? m[0].toLowerCase() : "";
  357.   };
  358.   
  359.   this.sanitizeList = function(sl) {
  360.     for (var j = sl.length; j-- > 0; ) {
  361.       sl[j] = this.getSite(sl[j]);
  362.     }
  363.     return sl;
  364.   };
  365.   
  366.   this.sanitizeMap = function(sm) {
  367.     var site;
  368.     delete sm[""];
  369.     for (var url in sm) {
  370.       site = this.getSite(url);
  371.       if (site != url) {
  372.         if (site) sm[site] = sm[url];
  373.         delete sm[url];
  374.       }
  375.     }
  376.     return sm;
  377.   };
  378.   
  379.   this.sanitizeString = function(s) {
  380.     // s = s.replace(/,/g,' ').replace(/\s{2,}/g,' ').replace(/(^\s+|\s+$)/g,'');
  381.     return this.set2string(this.string2set(s)); 
  382.   };
  383.   
  384.   this.string2set = function(s) {
  385.     return this.sortedSet(this.sanitizeList(this.splitString(s)));
  386.   };
  387.   
  388.   this.set2string = function(ss) {
  389.     return ss.join(" ");
  390.   };
  391.   
  392.   this.crop = function(url, width, max) {
  393.     width = width || 100;
  394.     if (url.length < width) return url;
  395.     
  396.     max = max || 2000;
  397.     if (max > width && url.length > max) {
  398.         return this.crop(url.substring(0, max / 2)) + "\n[...]\n" + 
  399.           this.crop(url.substring(url.length - max / 2));
  400.     }
  401.     
  402.     var parts = [];
  403.    
  404.     while (url.length > width) {
  405.       parts.push(url.substring(0, width));
  406.       url = url.substring(width);
  407.     }
  408.     parts.push(url);
  409.     return parts.join("\n");
  410.   };
  411. }
  412.  
  413.  
  414. const DOMUtils = {
  415.   lookupMethod: Components.utils ? Components.utils.lookupMethod : Components.lookupMethod,
  416.   consoleDump: false,
  417.   dump: function(msg) {
  418.     if(this.consoleDump) dump("[NoScript DOMUtils] " + msg + "\n");
  419.   },
  420.   findBrowser: function(chrome, win) {
  421.     var gb = chrome.getBrowser();
  422.     var browsers;
  423.     if (!(gb && (browsers = gb.browsers))) return null;
  424.     
  425.     var browser = gb.selectedBrowser;
  426.     if (browser.contentWindow == win) return browser;
  427.     
  428.     for (var j = browsers.length; j-- > 0;) {
  429.       browser = browsers[j];
  430.       if (browser.contentWindow == win) return browser;
  431.     }
  432.     
  433.     return null;
  434.   },
  435.   
  436.   findBrowserForNode: function(ctx) {
  437.     if (!ctx) return null;
  438.     var bi = null;
  439.     try {
  440.       if (!(ctx instanceof CI.nsIDOMWindow)) {
  441.         if (ctx instanceof CI.nsIDOMDocument) {
  442.           ctx = ctx.defaultView;
  443.         } else if(ctx instanceof CI.nsIDOMNode) {
  444.           ctx = ctx.ownerDocument.defaultView;
  445.         } else return null; 
  446.       }
  447.       if (!ctx) return null;
  448.       ctx = this.lookupMethod(ctx, "top")();
  449.       
  450.       var bi = this.createBrowserIterator(this.getChromeWindow(ctx));
  451.       for (var b; b = bi.next();) {
  452.         try {
  453.           if (b.contentWindow == ctx) return b;
  454.         } catch(e1) {
  455.           this.dump("Skipping browser iteration: " + e1);
  456.         }
  457.       }
  458.       this.dump("Browser not found for " + ctx);
  459.     } catch(e2) {
  460.       this.dump("Can't find browser for " + ctx + ": " + e2);
  461.     } finally {
  462.       if (bi) bi.dispose();
  463.       ctx = null;
  464.     }
  465.    
  466.     return null;
  467.   },
  468.   
  469.   
  470.   
  471.   getDocShellFromWindow: function(window) {
  472.     try {
  473.       return window.QueryInterface(CI.nsIInterfaceRequestor)
  474.                    .getInterface(CI.nsIWebNavigation)
  475.                    .QueryInterface(CI.nsIDocShell);
  476.     } catch(e) {
  477.       return null;
  478.     }
  479.   },
  480.     
  481.   getChromeWindow: function(window) {
  482.     try {
  483.       return this.lookupMethod(this.getDocShellFromWindow(window)
  484.         .QueryInterface(CI.nsIDocShellTreeItem).rootTreeItem
  485.         .QueryInterface(CI.nsIInterfaceRequestor)
  486.         .getInterface(CI.nsIDOMWindow), "window")();
  487.     } catch(e) {
  488.       return null;
  489.     }
  490.   },
  491.   
  492.   _wm: null,
  493.   get windowMediator() {
  494.     return this._wm || (this._wm = 
  495.         CC['@mozilla.org/appshell/window-mediator;1']
  496.                   .getService(CI.nsIWindowMediator));
  497.   },
  498.   
  499.   _winType: null,
  500.   perWinType: function(delegate) {
  501.     var wm = this.windowMediator;
  502.     var w = null;
  503.     var aa = Array.prototype.slice.call(arguments);
  504.     for each(var type in ['navigator:browser', 'emusic:window', 'Songbird:Main']) {
  505.      aa[0] = type;
  506.       w = delegate.apply(wm, aa);
  507.       if (w) {
  508.         this._winType = type;
  509.         break;
  510.       }
  511.     }
  512.     return w;
  513.   },
  514.   get mostRecentBrowserWindow() {
  515.     var res = this._winType && this.windowMediator.getMostRecentWindow(this._winType, true);
  516.     return res || this.perWinType(this.windowMediator.getMostRecentWindow, true);
  517.   },
  518.   
  519.   get windowEnumerator() {
  520.     var res = this._winType && this.windowMediator.getZOrderDOMWindowEnumerator(this._winType, true);
  521.     return res || this.perWinType(this.windowMediator.getZOrderDOMWindowEnumerator, true);
  522.   },
  523.   createBrowserIterator: function(initialWin) {
  524.     return new BrowserIterator(initialWin);
  525.   }
  526. };
  527.  
  528. function BrowserIterator(initialWin) {
  529.   if (!initialWin) {
  530.     initialWin = DOMUtils.mostRecentBrowserWindow;
  531.   }
  532.   this.currentWin = this.initialWin = initialWin;
  533.   this.initPerWin();
  534. }
  535. BrowserIterator.prototype = {
  536.  
  537.   initPerWin: function() {
  538.     var currentTB = this.currentWin && this.currentWin.getBrowser && this.currentWin.getBrowser();
  539.     if (currentTB) {
  540.       this.browsers = currentTB.browsers;
  541.       this.currentTab = currentTB.selectedBrowser;
  542.     } else if(this.currentWin) {
  543.       try {
  544.         this.browsers = this.currentWin.document.getElementsByTagName("browser");
  545.         this.currentTab = this.browsers.length && this.browsers[0] || null;
  546.       } catch(e) {
  547.          this.currentTab = null;
  548.       }
  549.     } else this.currentTab = null;
  550.     this.mostRecentTab = this.currentTab;
  551.     this.curTabIdx = 0;
  552.   },
  553.   next: function() {
  554.     var ret = this.currentTab;
  555.     this.currentTab = null;
  556.     if(ret) return ret;
  557.     if(!this.initialWin) return null;
  558.     if (this.curTabIdx >= this.browsers.length) {
  559.       if (!this.winEnum) {
  560.         this.winEnum = DOMUtils.windowEnumerator;
  561.       }
  562.       if (this.winEnum.hasMoreElements()) {
  563.         this.currentWin = this.winEnum.getNext();
  564.         if (this.currentWin != this.initialWin){
  565.            this.initPerWin();
  566.         }
  567.         return this.next();
  568.       } else {
  569.         this.dispose();
  570.         return null;
  571.       }
  572.     }
  573.     this.currentTab = this.browsers[this.curTabIdx++];
  574.     if (this.currentTab == this.mostRecentTab) this.next();
  575.     return this.next();
  576.   },
  577.   dispose: function() {
  578.     if (!this.initialWin) return; // already disposed;
  579.     this.initialWin = 
  580.       this.currentWin = 
  581.       this.browsers = 
  582.       this.currentTab = 
  583.       this.mostRecentTab = 
  584.       this.winEnum = 
  585.       null;
  586.   },
  587.   
  588.   find: function(filter) {
  589.     try {
  590.       for (var b; b = this.next();) {
  591.         if (filter(b)) {
  592.           return b;
  593.         }
  594.       }
  595.     } finally {
  596.       this.dispose();
  597.       filter = null;
  598.     }
  599.     return null;
  600.   }
  601. };
  602.  
  603. function PolicySites(sitesString) {
  604.   if (sitesString) this.sitesString = sitesString;
  605. }
  606. PolicySites.prototype = {
  607.   clone: function() {
  608.     return new PolicySites(this.sitesString);
  609.   }
  610. ,
  611.   equals: function(other) {
  612.     return other && (this.sitesString == other.sitesString);
  613.   }
  614. ,
  615.   _sitesString: "",
  616.   get sitesString() {
  617.     return this._sitesString;
  618.   },
  619.   set sitesString(s) {
  620.     s = SiteUtils.sanitizeString(s);
  621.     if (s != this._sitesString) {
  622.       this._sitesString = s;
  623.       this._sitesMap = null;
  624.       this._sitesList = null;
  625.     }
  626.     return s;
  627.   }
  628. ,
  629.   _sitesList: null,
  630.   get sitesList() {
  631.     return this._sitesList ? this._sitesList : this._sitesList = SiteUtils.splitString(this.sitesString);
  632.   },
  633.   set sitesList(sl) {
  634.     this.sitesString = SiteUtils.set2string(SiteUtils.sortedSet(SiteUtils.sanitizeList(sl)));
  635.     return this.sitesList;
  636.   }
  637. ,
  638.   _sitesMap: null,
  639.   get sitesMap() {
  640.     if (!this._sitesMap) {
  641.       const sm = {};
  642.       const sl = SiteUtils.splitString(this.sitesString);
  643.       if (sl) {
  644.         for (var j = sl.length; j-- > 0;) {
  645.           sm[sl[j]] = true;
  646.         }
  647.       }
  648.       this._sitesMap = sm;
  649.     }
  650.     return this._sitesMap;
  651.   },
  652.   set sitesMap(sm) {
  653.     sm = sm ? SiteUtils.sanitizeMap(sm) : {};
  654.     var sl = [];
  655.     for (var s in sm) {
  656.       sl[sl.length] = s;
  657.     }
  658.     
  659.     this._sitesString = SiteUtils.set2string(SiteUtils.sort(sl));
  660.     this._sitesList = null;
  661.     return this._sitesMap = sm;
  662.   }
  663. ,
  664.   fromPref: function(pref) {
  665.    this.sitesString = pref.getCharPref("sites")
  666.        .replace(/[^\u0000-\u007f]+/g, function($0) { return decodeURIComponent(escape($0)) });
  667.   }
  668. ,
  669.   settingPref: false,
  670.   toPref: function(pref) {
  671.     
  672.     if (pref.prefIsLocked("sites")) {
  673.       this.fromPref(pref);
  674.       return;
  675.     }
  676.     var change;
  677.     var s = this.sitesString.replace(/[^\u0000-\u007f]+/g,function($0) { return unescape(encodeURIComponent($0)) });
  678.     try {
  679.       change = s != pref.getCharPref("sites");
  680.     } catch(ex) {
  681.       change = true;
  682.     }
  683.     
  684.     if (change) {
  685.       this.settingPref = true;
  686.       pref.setCharPref("sites", s);
  687.       this.settingPref = false;
  688.     }
  689.   }
  690. ,
  691.   // returns the shortest match for a site, or "" if no match is found
  692.   matches: function(site) {
  693.     if (!site) return "";
  694.     const sm = this.sitesMap;
  695.     var match;
  696.     var dots; // track "dots" for (temporary) fix to 2nd level domain policy lookup flaw 
  697.     var pos = site.indexOf(':') + 1;
  698.     if (pos > 0 && (pos == site.length || site[pos] == '/')) {
  699.       if (sm[match = site.substring(0, pos)]) return match; // scheme match
  700.       if (++pos >= site.length || site[pos] != '/') return "";
  701.       match = site.substring(pos + 1);
  702.       dots = 0;
  703.     } else {
  704.       match = site;
  705.       dots = 1;
  706.     }
  707.  
  708.     var submatch;
  709.     for (pos = match.lastIndexOf('.'); pos > 1; dots++) {
  710.       pos = match.lastIndexOf('.', pos - 1);
  711.       if ((dots || pos > -1) && sm[submatch = match.substring(pos + 1)]) {
  712.         return submatch; // domain/subdomain match
  713.       }
  714.     }
  715.     
  716.     if (sm[match]) return match; // host match
  717.     return sm[site] ? site : ""; // full match
  718.   }
  719. ,
  720.  
  721.  
  722.   _remove: function(site) {
  723.     const sm = this.sitesMap;
  724.     delete sm[site];
  725.     if (site.indexOf(":") < 0 && site.indexOf(".") == site.lastIndexOf(".")) {
  726.       // base domain hack
  727.       delete sm["http://" + site];
  728.       delete sm["https://" + site];
  729.       delete sm["file://" + site];
  730.     }
  731.   },
  732.   remove: function(sites, keepUp, keepDown) {
  733.     if (!sites) return false;
  734.     if (!(typeof(sites) == "object" && "push" in sites)) 
  735.       return this.remove([sites], keepUp, keepDown);
  736.     keepUp = keepUp || false;
  737.     keepDown = keepDown || false;
  738.     
  739.     const sm = this.sitesMap;
  740.     var change = false;
  741.     var site, match;
  742.     var tmp= keepDown ? null : new PolicySites();
  743.     for (var j = sites.length; j-- > 0;) {
  744.       site = sites[j];
  745.       if (site[site.length-1] != ":") { // not a scheme only site
  746.         if (!keepUp) {
  747.           while ((match = this.matches(site)) && site != match) { // remove ancestors
  748.             this._remove(match);
  749.             change = true;
  750.           }
  751.         }
  752.         if (!keepDown) {
  753.           tmp.sitesString = site;
  754.           for (match in sm) { // remove descendants
  755.             if (tmp.matches(match)) {
  756.               if (site != match) delete sm[match];
  757.               change = true;
  758.             }
  759.           }
  760.           this._remove(site);
  761.         }
  762.       }
  763.     
  764.       if (site in sm) {
  765.         this._remove(site);
  766.         change = true;
  767.       }
  768.     }
  769.     if (change) this.sitesMap = this._sitesMap;
  770.     return change;
  771.   },
  772.   
  773.   _add: function(site) {
  774.     return (site in this.sitesMap ? false : this.sitesMap[site] = true);
  775.   },
  776.   
  777.   add: function(sites) {
  778.     if (!sites) return false;
  779.     if (!(typeof(sites) == "object" && "push" in sites)) 
  780.       return this.add([sites]);
  781.     
  782.     var change = false;
  783.     var site;
  784.     for (var j = sites.length; j-- > 0;) {
  785.       site = sites[j];
  786.       if (site.indexOf(":") < 0 && site.indexOf(".") == site.lastIndexOf(".")) {
  787.         // base domain hack
  788.         if(this._add("http://" + site)) change = true;
  789.         if(this._add("https://" + site)) change = true;
  790.         if(this._add("file://" + site)) change = true;
  791.       }
  792.       if (this._add(site)) change = true;
  793.     }
  794.     if (change) this.sitesMap = this._sitesMap;
  795.     return change;
  796.   }
  797. }
  798.  
  799. function NoscriptService() {
  800.   this.register();
  801. }
  802.  
  803. NoscriptService.prototype = {
  804.   VERSION: "1.7.6.4",
  805.   
  806.   get wrappedJSObject() {
  807.     return this;
  808.   }
  809. ,
  810.   QueryInterface: xpcom_generateQI(SERVICE_IIDS),
  811.   generateQI: xpcom_generateQI
  812. ,
  813.   // nsIObserver implementation 
  814.   observe: function(subject, topic, data) {
  815.     // dump(SERVICE_NAME+" notified of "+subject+","+topic+","+data); //DEBUG
  816.     if (subject instanceof CI.nsIPrefBranch2) {
  817.       this.syncPrefs(subject, data);
  818.     } else {
  819.       switch (topic) {
  820.         case "xpcom-shutdown":
  821.           this.unregister();
  822.           break;
  823.         case "profile-before-change": 
  824.           this.dispose();
  825.           break;
  826.         case "profile-after-change":
  827.           try {
  828.             this.init();
  829.           } catch(e) {
  830.             this.dump("Init error -- " + e.message);
  831.           }
  832.           break;
  833.         case "em-action-requested":
  834.           if ((subject instanceof CI.nsIUpdateItem)
  835.               && subject.id == EXTENSION_ID ) {
  836.             if (data == "item-uninstalled" || data == "item-disabled") {
  837.               this.uninstalling = true;
  838.             } else if (data == "item-enabled") {
  839.               this.uninstalling = false;
  840.             }
  841.           }
  842.         break;
  843.         case "toplevel-window-ready":
  844.           this.registerToplevel(subject); 
  845.         break;
  846.       }
  847.     }
  848.   },
  849.   
  850.   registerToplevel: function(window) {
  851.     
  852.     if ((window instanceof CI.nsIDOMChromeWindow) && !window.opener &&
  853.        (window instanceof CI.nsIDOMNSEventTarget)) {
  854.       window.isNewToplevel = true;
  855.       if (this.consoleDump & LOG_CHROME_WIN) {
  856.         this.dump("Toplevel register, true");
  857.       }
  858.       this.handleToplevel.ns = this;
  859.       window.addEventListener("load", this.handleToplevel, false);
  860.     }
  861.   },
  862.   handleToplevel: function(ev) {
  863.     // this resets newtoplevel status to true after chrome
  864.     const window = ev.currentTarget;
  865.     const callee = arguments.callee;
  866.     const ns = callee.ns;
  867.     switch (ev.type) {
  868.       case "load":
  869.         window.removeEventListener("load", callee, false);
  870.         window.addEventListener("unload", callee, false);
  871.         ns.delayExec(callee, 0, { type: "timeout", currentTarget: window });
  872.         break;
  873.       case "timeout":
  874.       case "unload":
  875.         window.isNewToplevel = false;
  876.         window.removeEventListener("unload", callee, false);
  877.     }
  878.     if (ns.consoleDump & LOG_CHROME_WIN) 
  879.       ns.dump("Toplevel " + ev.type + ", " + window.isNewToplevel);
  880.   },
  881.   
  882.   register: function() {
  883.     const osvr = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
  884.     osvr.addObserver(this, "profile-before-change", true);
  885.     osvr.addObserver(this, "xpcom-shutdown", true);
  886.     osvr.addObserver(this, "profile-after-change", true);
  887.     osvr.addObserver(this, "toplevel-window-ready", true);
  888.   }
  889. ,
  890.   unregister: function() {
  891.     try {
  892.       const osvr = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
  893.       osvr.removeObserver(this, "profile-before-change");
  894.       osvr.removeObserver(this, "xpcom-shutdown");
  895.       osvr.removeObserver(this, "profile-after-change");
  896.       osvr.removeObserver(this, "toplevel-window-ready");
  897.     } catch(e) {
  898.       this.dump(e + " while unregistering.");
  899.     }
  900.   }
  901. ,
  902.   
  903.   // Preference driven properties
  904.   autoAllow: false,
  905.  
  906.   blockCrossIntranet: true,
  907.   blockNSWB: false,
  908.   
  909.   consoleDump: 0,
  910.   consoleLog: false,
  911.   
  912.   truncateTitle: true,
  913.   truncateTitleLen: 255,
  914.   
  915.   showPlaceholder: true,
  916.   showUntrustedPlaceholder: true,
  917.   collapseObject: false,
  918.   
  919.   forbidSomeContent: false,
  920.   contentBlocker: false,
  921.   
  922.   forbidChromeScripts: false,
  923.   forbidData: true,
  924.   
  925.   forbidJarDocuments: true,
  926.   forbidJarDocumentsExceptions: null,
  927.   
  928.   forbidJava: true,
  929.   forbidFlash: false,
  930.   forbidFlash: true,
  931.   forbidPlugins: false,
  932.   forbidIFrames: false, 
  933.   forbidIFramesContext: 1, // 0 = all iframes, 1 = different site, 2 = different domain, 3 = different base domain
  934.   
  935.   alwaysBlockUntrustedContent: true,
  936.   docShellJSBlocking: 1, // 0 - don't touch docShells, 1 - block untrusted, 2 - block not whitelisted
  937.   
  938.   forbidXBL: 4,
  939.   forbidXHR: 2,
  940.   injectionCheck: 2,
  941.   injectionCheckSubframes: true,
  942.   
  943.   jsredirectIgnore: false,
  944.   jsredirectFollow: false,
  945.   jsredirectForceShow: false,
  946.   
  947.   jsHack: null,
  948.   jsHackRegExp: null,
  949.   silverlightPatch: false,
  950.   
  951.   nselNever: false,
  952.   nselForce: true,
  953.  
  954.   filterXGetRx: "(?:<+(?=[^<>=\\d\\. ])|[\\\\'\"\\x00-\\x07\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F])",
  955.   filterXGetUserRx: "",
  956.   
  957.   
  958.   whitelistRegExp: null,
  959.   allowedMimeRegExp: null, 
  960.   hideOnUnloadRegExp: null,
  961.   requireReloadRegExp: null,
  962.   
  963.   resetDefaultPrefs: function(prefs, exclude) {
  964.     exclude = exclude || [];
  965.     var children = prefs.getChildList("", {});
  966.     for (var j = children.length; j-- > 0;) {
  967.       if (exclude.indexOf(children[j]) < 0) {
  968.         if (prefs.prefHasUserValue( children[j])) {
  969.           dump("Resetting noscript." + children[j] + "\n");
  970.           try {
  971.             prefs.clearUserPref(children[j]);
  972.           } catch(e) { dump(e + "\n") }
  973.         }
  974.       }
  975.     }
  976.     this.savePrefs();
  977.   },
  978.   
  979.   resetDefaultGeneralPrefs: function() {
  980.     this.resetDefaultPrefs(this.prefs, ['version']);
  981.   },
  982.   
  983.   resetDefaultSitePrefs: function() {
  984.     this.eraseTemp();
  985.     this.setJSEnabled(this.splitList(this.getPref("default")), true, true);
  986.   },
  987.   
  988.   resetDefaults: function() {
  989.     this.resetDefaultGeneralPrefs();
  990.     this.resetDefaultSitePrefs();
  991.   },
  992.   syncPrefs: function(branch, name) {
  993.     switch (name) {
  994.       case "sites":
  995.         if (this.jsPolicySites.settingPref) return;
  996.         if (this.locked) this.defaultCaps.lockPref(this.POLICY_NAME + ".sites");
  997.         try {
  998.           this.jsPolicySites.fromPref(this.policyPB);
  999.         } catch(ex) {
  1000.           this.resetDefaultSitePrefs();
  1001.         }
  1002.         break;
  1003.       case "temp":
  1004.         this.tempSites.sitesString = this.getPref(name, "");
  1005.         break;
  1006.       case "untrusted":
  1007.         this.untrustedSites.sitesString = this.getPref(name, "");
  1008.         break;
  1009.       case "default.javascript.enabled":
  1010.           if (dc.getCharPref(name) != "noAccess") {
  1011.             dc.unlockPref(name);
  1012.             dc.setCharPref(name, "noAccess");
  1013.           }
  1014.          dc.lockPref(name);
  1015.          break;
  1016.       case "enabled":
  1017.         try {
  1018.           this.mozJSEnabled = this.mozJSPref.getBoolPref("enabled");
  1019.         } catch(ex) {
  1020.           this.mozJSPref.setBoolPref("enabled", this.mozJSEnabled = true);
  1021.         }
  1022.       break;
  1023.       case "forbidJava":
  1024.       case "forbidFlash":
  1025.       case "forbidSilverlight":
  1026.       case "forbidPlugins":
  1027.       case "forbidIFrames":
  1028.         this[name]=this.getPref(name, this[name]);
  1029.         this.forbidSomeContent = this.forbidJava || this.forbidFlash 
  1030.             || this.forbidSilverlight || this.forbidPlugins || this.forbidIFrames;
  1031.       break;
  1032.       
  1033.       
  1034.       case "filterXPost":
  1035.       case "filterXGet":
  1036.       case "blockXIntranet":
  1037.       case "safeToplevel":
  1038.       case "autoAllow":
  1039.       case "contentBlocker":
  1040.       case "docShellJSBlocking":
  1041.       case "showUntrustedPlaceholder":
  1042.       case "collapseObject":
  1043.       case "truncateTitle":
  1044.       case "truncateTitleLen":
  1045.       case "forbidChromeScripts":
  1046.       case "forbidData":
  1047.       case "forbidJarDocuments":
  1048.       case "forbidMetaRefresh":
  1049.       case "forbidIFramesContext":
  1050.       case "forbidXBL":
  1051.       case "forbidXHR":
  1052.       case "injectionCheck":
  1053.       case "jsredirectFollow":
  1054.       case "jsredirectIgnore":
  1055.       case "jsredirectForceShow":
  1056.       case "jsHack":
  1057.       case "consoleLog":
  1058.       case "silverlightPatch":
  1059.         this[name] = this.getPref(name, this[name]);
  1060.       break;
  1061.       case "consoleDump":
  1062.         this[name] = this.getPref(name, this[name]);
  1063.         this.injectionChecker.logEnabled = this.consoleDump & LOG_INJECTION_CHECK;
  1064.         this.domUtils.consoleDump = this.consoleDump & LOG_DOMUTILS;
  1065.       break;
  1066.       case "global":
  1067.         this.globalJS = this.getPref(name, false);
  1068.       break;
  1069.       
  1070.       case "alwaysBlockUntrustedContent":
  1071.         this[name] = this.getPref(name, this[name]);
  1072.         this.initContentPolicy();
  1073.       break;
  1074.       
  1075.       case "forbidMetaRefreshRemember":
  1076.         if (!this.getPref(name)) this.metaRefreshWhitelist = {};
  1077.       break;
  1078.       
  1079.       // single rx
  1080.       case "filterXGetRx":
  1081.       case "filterXGetUserRx":
  1082.         this.updateRxPref(name, this[name], "g");
  1083.       break;
  1084.       
  1085.       // multiple rx
  1086.       case "forbidJarDocumentsExceptions":
  1087.       case "filterXExceptions":
  1088.       case "jsHackRegExp":
  1089.         this.updateRxPref(name, "", "", this.rxParsers.multi);
  1090.       break;
  1091.       
  1092.       // multiple rx autoanchored
  1093.       case "hideOnUnloadRegExp":
  1094.         this.updateStyleSheet("." + this.hideObjClassName + " {display: none !important}", true);
  1095.       case "allowedMimeRegExp":
  1096.       case "requireReloadRegExp":
  1097.       case "whitelistRegExp":
  1098.         this.updateRxPref(name, "", "^", this.rxParsers.multi);
  1099.       break;
  1100.         
  1101.         
  1102.       case "safeJSRx":
  1103.         this.initSafeJSRx();
  1104.       break;
  1105.       
  1106.       case "allowClipboard":
  1107.         this.updateExtraPerm(name, "Clipboard", ["cutcopy", "paste"]);
  1108.       break;
  1109.       case "allowLocalLinks":
  1110.         this.updateExtraPerm(name, "checkloaduri", ["enabled"]);
  1111.       break;
  1112.       
  1113.       case "blockNSWB":
  1114.       case "nselForce":
  1115.       case "nselNever":
  1116.       case "showPlaceholder":
  1117.         this.updateCssPref(name);
  1118.         if ((name == "nselNever") && this.getPref("nselNever") && !this.blockNSWB) {
  1119.           this.setPref("blockNSWB", true);
  1120.         }
  1121.       break;
  1122.       
  1123.       case "policynames":
  1124.         this.setupJSCaps();
  1125.       break;
  1126.     }
  1127.   },
  1128.   
  1129.   rxParsers: {
  1130.     simple: function(s, flags) {
  1131.       return new RegExp(s, flags);
  1132.     },
  1133.     multi: function(s, flags) {
  1134.       var anchor = /\^/.test(flags);
  1135.       if(anchor) flags = flags.replace(/\^/g, '');
  1136.        
  1137.       var lines = s.split(/[\n\r]+/);
  1138.       var rxx = [];
  1139.       var l;
  1140.       for (var j = lines.length; j-- > 0;) {
  1141.         l = lines[j];
  1142.         if (/\S/.test(l)) {
  1143.           if(anchor && l[0] != '^') {
  1144.             l = '^' + l + '$';
  1145.           }
  1146.           rxx.push(new RegExp(l, flags));
  1147.         } else {
  1148.           lines.splice(j, 1);
  1149.         }
  1150.       }
  1151.       if (!rxx.length) return null;
  1152.       
  1153.       rxx.test = function(s) {
  1154.         for (var j = this.length; j-- > 0;) {
  1155.           if (this[j].test(s)) return true;
  1156.         }
  1157.         return false;
  1158.       }
  1159.       rxx.toString = function() { return lines.join("\n"); }
  1160.       return rxx;
  1161.     }
  1162.   },
  1163.   updateRxPref: function(name, def, flags, parseRx) {
  1164.     parseRx = parseRx || this.rxParsers.simple;
  1165.     var s = this.getPref(name, def);
  1166.     if (!s) {
  1167.       this[name] = null;
  1168.     } else
  1169.     {
  1170.       try {
  1171.         this[name] = parseRx(this.getPref(name, def), flags);
  1172.       } catch(e) {
  1173.         if(this.consoleDump) this.dump("Error parsing regular expression " + name + ", " + e);
  1174.         this[name] = parseRx(def, flags);
  1175.       }
  1176.     }
  1177.   },
  1178.   
  1179.   
  1180.   updateExtraPerm: function(prefName, baseName, names) {
  1181.     var cpName;
  1182.     var enabled = this.getPref(prefName, false);
  1183.     for (var j = names.length; j-- > 0;) {
  1184.       cpName = this.POLICY_NAME + "." + baseName + "." + names[j];
  1185.       try {
  1186.         if (enabled) {
  1187.           this.caps.setCharPref(cpName,"allAccess");
  1188.         } else {
  1189.           if (this.caps.prefHasUserValue(cpName)) {
  1190.             this.caps.clearUserPref(cpName);
  1191.           }
  1192.         }
  1193.       } catch(ex) {}
  1194.     }
  1195.   },
  1196.   
  1197.   updateCssPref: function(name) {
  1198.     var value = this[name] = this.getPref(name, value);
  1199.     var sheet;
  1200.     switch(name) {
  1201.       case "nselForce":
  1202.         sheet = "noscript.noscript-show, span.noscript-show { display: inline !important } span.noscript-show { padding: 0px; margin: 0px; border: none; background: inherit; color: inherit }";
  1203.         break;
  1204.       case "nselNever":
  1205.         sheet = "noscript, noscript * { display: none !important }";
  1206.         break;
  1207.       case "blockNSWB": 
  1208.         sheet = "noscript, noscript * { background-image: none !important; list-style-image: none !important }";
  1209.         break;
  1210.       case "showPlaceholder": 
  1211.         sheet = '.__noscriptPlaceholder__ > .__noscriptPlaceholder__1 { display: block !important; -moz-outline-color: #fc0 !important; -moz-outline-style: solid !important; -moz-outline-width: 1px !important; -moz-outline-offset: -1px !important; cursor: pointer !important; background: #ffffe0 url("' + 
  1212.                     this.pluginPlaceholder + '") no-repeat left top !important; opacity: 0.6 !important; margin-top: 0px !important; margin-bottom: 0px !important;} ' +
  1213.                 '.__noscriptPlaceholder__1 > .__noscriptPlaceholder__2 { display: block !important; background-repeat: no-repeat !important; background-color: transparent !important; width: 100%; height: 100%; display: block; margin: 0px; border: none } ' +
  1214.                 'noscript .__noscriptPlaceholder__ { display: inline !important; }';
  1215.         break;
  1216.         
  1217.       default:
  1218.         return;
  1219.     };
  1220.     this.updateStyleSheet(sheet, value);
  1221.   },
  1222.   
  1223.   updateStyleSheet: function(sheet, enabled) {
  1224.     const sssClass = CC["@mozilla.org/content/style-sheet-service;1"];
  1225.     if (!sssClass) return;
  1226.     
  1227.     const sss = sssClass.getService(CI.nsIStyleSheetService);
  1228.     const uri = SiteUtils.ios.newURI("data:text/css," + sheet, null, null);
  1229.     if (sss.sheetRegistered(uri, sss.USER_SHEET)) {
  1230.       if (!enabled) sss.unregisterSheet(uri, sss.USER_SHEET);
  1231.     } else {
  1232.       try {
  1233.         if (enabled) sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
  1234.       } catch(e) {
  1235.         this.log("[NoScript CSS] Can't register " + uri + ", " + e); 
  1236.       }
  1237.     }
  1238.   },
  1239.  
  1240.   getString: function(name, parms) { return noscriptStrings.getString(name, parms); },
  1241.   
  1242.   _uninstalling: false,
  1243.   get uninstalling() {
  1244.     return this._uninstalling;
  1245.   },
  1246.   set uninstalling(b) {
  1247.     if (!this._uninstalling) {
  1248.       if (b) this.uninstallJob();
  1249.     } else {
  1250.       if (!b) this.undoUninstallJob();
  1251.     }
  1252.     return this._uninstalling = b;
  1253.   }
  1254. ,
  1255.   _inited: false,
  1256.   POLICY_NAME: "maonoscript",
  1257.   prefService: null,
  1258.   caps: null,
  1259.   defaultCaps: null,
  1260.   policyPB: null,
  1261.   prefs: null,
  1262.   mozJSPref: null,
  1263.   mozJSEnabled: true,
  1264.   disabled: false
  1265. ,
  1266.   // random resource aliases
  1267.   contentBase: null,
  1268.   skinBase: null,
  1269.   hideObjClassName: "__noscriptHideObj__",
  1270.   get hideObjClassNameRx() {
  1271.     var rx = new RegExp("\\b" + this.hideObjClassName + "\\s*", "g");
  1272.     this.__defineGetter__("hideObjClassNameRx", function() { return rx; });
  1273.     return rx;
  1274.   },
  1275.   pluginPlaceholder: "",
  1276.   
  1277.   _initResources: function() {
  1278.     const ios = SiteUtils.ios;
  1279.     var resProt = ios.getProtocolHandler("resource").QueryInterface(CI.nsIResProtocolHandler);
  1280.     var base;
  1281.     for each(var r in ["skin", "content"]) {
  1282.       base = "noscript_" + Math.random();
  1283.       resProt.setSubstitution(base, ios.newURI("chrome:noscript/" + r + "/", null, null));
  1284.       this[r + "Base"] = "resource://" + base + "/";
  1285.     }
  1286.     this.pluginPlaceholder = this.skinBase + "icon32.png";
  1287.   },
  1288.   
  1289.   init: function() {
  1290.     if (this._inited) return false;
  1291.     try {
  1292.       SiteUtils.ios.newChannel("chrome://noscript/content/", null, null).open().close();
  1293.     } catch(e) {
  1294.       this.disabled = true;
  1295.       dump("NoScript disabled on this profile\n");
  1296.       return false;
  1297.     }
  1298.     this._inited = true;
  1299.     
  1300.     this._initResources();
  1301.  
  1302.     this.initTldService();
  1303.     
  1304.     this.xcache = new XCache();
  1305.     
  1306.     const osvr = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
  1307.     if (!this.requestWatchdog) {
  1308.       osvr.addObserver(this.requestWatchdog = new RequestWatchdog(this), "http-on-modify-request", true);
  1309.       osvr.addObserver(this.requestWatchdog, "http-on-examine-response", true);
  1310.     }
  1311.     osvr.addObserver(this, "em-action-requested", true);
  1312.     
  1313.     const dls = CC['@mozilla.org/docloaderservice;1'].getService(CI.nsIWebProgress);
  1314.     dls.addProgressListener(this, CI.nsIWebProgress.NOTIFY_LOCATION | CI.nsIWebProgress.NOTIFY_STATE_REQUEST);
  1315.  
  1316.  
  1317.     const prefserv = this.prefService = CC["@mozilla.org/preferences-service;1"]
  1318.       .getService(CI.nsIPrefService).QueryInterface(CI.nsIPrefBranch);
  1319.  
  1320.     const PBI=CI.nsIPrefBranch2;
  1321.     this.caps = prefserv.getBranch("capability.policy.").QueryInterface(PBI);
  1322.     this.defaultCaps = prefserv.getDefaultBranch(this.caps.root);
  1323.  
  1324.     this.policyPB = prefserv.getBranch("capability.policy." + this.POLICY_NAME + ".").QueryInterface(PBI);
  1325.     this.prefs = prefserv.getBranch("noscript.").QueryInterface(PBI);
  1326.     
  1327.     this.policyPB.addObserver("sites", this, true);
  1328.     
  1329.     this.prefs.addObserver("", this, true);
  1330.     this.mozJSPref = prefserv.getBranch("javascript.").QueryInterface(PBI);
  1331.     this.mozJSPref.addObserver("enabled", this, true);
  1332.     
  1333.     this.permanentSites.sitesString = this.getPref("mandatory", "chrome: about: resource:");
  1334.     
  1335.     this.captureExternalProtocols();
  1336.     
  1337.     for each(var p in [
  1338.       "autoAllow",
  1339.       "allowClipboard", "allowLocalLinks",
  1340.       "allowedMimeRegExp", "hideOnUnloadRegExp", "requireReloadRegExp",
  1341.       "blockCrossIntranet",
  1342.       "blockNSWB",
  1343.       "consoleDump", "consoleLog", "contentBlocker",
  1344.       "docShellJSBlocking",
  1345.       "filterXPost", "filterXGet", 
  1346.       "filterXGetRx", "filterXGetUserRx", 
  1347.       "filterXExceptions",
  1348.       "forbidChromeScripts",
  1349.       "forbidJarDocuments", "forbidJarDocumentsExceptions",
  1350.       "forbidJava", "forbidFlash", "forbidSilverlight", "forbidPlugins", 
  1351.       "forbidIFrames", "forbidIFramesContext", "forbidData",
  1352.       "forbidMetaRefresh",
  1353.       "forbidXBL", "forbidXHR",
  1354.       "alwaysBlockUntrustedContent",
  1355.       "global",
  1356.       "injectionCheck", "injectionCheckSubframes",
  1357.       "jsredirectIgnore", "jsredirectFollow", "jsredirectForceShow", "jsHack", "jsHackRegExp",
  1358.       "nselNever", "nselForce",
  1359.       "showPlaceholder", "showUntrustedPlaceholder", "collapseObject",
  1360.       "temp", "untrusted",
  1361.       "silverlightPatch",
  1362.       "truncateTitle", "truncateTitleLen",
  1363.       "whitelistRegExp",
  1364.       ]) {
  1365.       try {
  1366.         this.syncPrefs(this.prefs, p);
  1367.       } catch(e) {
  1368.         dump("[NoScript init error] " + e + " setting " + p + "\n");
  1369.       }
  1370.     }
  1371.     
  1372.     
  1373.     
  1374.     this.setupJSCaps();
  1375.     
  1376.     // locking management
  1377.     if ((this.locked = this.prefs.prefIsLocked("default"))) {
  1378.       try {
  1379.         const psKey = this.POLICY_NAME + ".sites";
  1380.         const dc = this.defaultCaps;
  1381.         dc.lockPref("policynames");
  1382.         dc.unlockPref(psKey);
  1383.         this.resetDefaultSitePrefs();
  1384.         dc.setCharPref(psKey, this.policyPB.getCharPref("sites"));
  1385.         dc.lockPref(psKey);
  1386.         if (dc instanceof PBI) dc.addObserver("default.javascript.", this, true);
  1387.         dc.setCharPref("default.javascript.enabled", "noAccess");
  1388.         dc.lockPref("default.javascript.enabled");
  1389.         this.prefs.lockPref("global");
  1390.       } catch(e) {
  1391.         this.dump(e);
  1392.       }
  1393.     } else {
  1394.       // init jsPolicySites from prefs
  1395.       this.syncPrefs(this.policyPB, "sites");
  1396.     }
  1397.     
  1398.     this.syncPrefs(this.mozJSPref, "enabled");
  1399.     
  1400.     if (this.getPref("tempGlobal", false))
  1401.       this.jsEnabled = false;
  1402.     
  1403.     this.eraseTemp();
  1404.     // this.sanitize2ndLevs();
  1405.     
  1406.     this.reloadWhereNeeded(); // init snapshot
  1407.     
  1408.     // hook on redirections (non persistent, otherwise crashes on 1.8.x)
  1409.     CC['@mozilla.org/categorymanager;1'].getService(CI.nsICategoryManager)
  1410.       .addCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, SERVICE_CTRID, false, true);
  1411.     
  1412.     return true;
  1413.   },
  1414.   
  1415.   dispose: function() {
  1416.     try {
  1417.       if(!this._inited) return;
  1418.       this._inited = false;
  1419.       
  1420.       this.shouldLoad = this.shouldProcess = CP_NOP;
  1421.       
  1422.       CC['@mozilla.org/categorymanager;1'].getService(CI.nsICategoryManager)
  1423.         .deleteCategoryEntry("net-channel-event-sinks", SERVICE_CTRID, SERVICE_CTRID, false);
  1424.       
  1425.       const osvr = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
  1426.       if (this.requestWatchdog) {
  1427.         osvr.removeObserver(this.requestWatchdog, "http-on-modify-request");
  1428.         osvr.removeObserver(this.requestWatchdog, "http-on-examine-response");
  1429.         this.requestWatchdog = null;
  1430.       }
  1431.       osvr.removeObserver(this, "em-action-requested");
  1432.             
  1433.       const dls = CC['@mozilla.org/docloaderservice;1'].getService(CI.nsIWebProgress);
  1434.       dls.removeProgressListener(this);
  1435.       
  1436.       this.prefs.removeObserver("", this);
  1437.       this.mozJSPref.removeObserver("enabled", this);
  1438.       this.resetJSCaps();
  1439.       this.resetPolicyState();
  1440.       
  1441.       if(this.consoleDump & LOG_LEAKS) this.reportLeaks();
  1442.     } catch(e) {
  1443.       this.dump(e + " while disposing.");
  1444.     }
  1445.   },
  1446.   
  1447.   
  1448.   reportLeaks: function() {
  1449.     // leakage detection
  1450.     this.dump("DUMPING " + this.__parent__);
  1451.     for(var v in this.__parent__) {
  1452.       this.dump(v + " = " + this.__parent__[v] + "\n");
  1453.     }
  1454.   },
  1455.   
  1456.   captureExternalProtocols: function() {
  1457.     try {
  1458.       const ph = this.prefService.getDefaultBranch("network.protocol-handler.");
  1459.       if (this.getPref("fixURI", true)) {
  1460.         try {
  1461.           ph.setBoolPref("expose-all", true);
  1462.         } catch(e1) {}
  1463.         var prots = [];
  1464.         for each(var key in ph.getChildList("expose.", {})) {
  1465.           try {
  1466.             ph.setBoolPref(key, true);
  1467.             prots.push(key.replace("expose.", ""));
  1468.             if (ph.hasUserPref(key)) ph.clearUserPref(key);
  1469.           } catch(e1) {}
  1470.         }
  1471.         if (prots.length) this.extraCapturedProtocols = prots;
  1472.       }
  1473.     } catch(e) {}
  1474.   },
  1475.   
  1476.   extraCapturedProtocols: null,
  1477.   
  1478.   permanentSites: new PolicySites(),
  1479.   isPermanent: function(s) {
  1480.     return s && this.permanentSites.matches(s);
  1481.   }
  1482. ,
  1483.   tempSites: new PolicySites(),
  1484.   isTemp: function(s) {
  1485.     return s in this.tempSites.sitesMap;
  1486.   }
  1487. ,
  1488.   setTemp: function(s, b) {
  1489.     var change = b 
  1490.       ? this.tempSites.add(s) 
  1491.       : this.tempSites.remove(s, true, true); // keeps up and down, see #eraseTemp() 
  1492.     if (change) {
  1493.       this.setPref("temp", this.tempSites.sitesString);
  1494.     }
  1495.   },
  1496.   
  1497.   untrustedSites: new PolicySites(),
  1498.   isUntrusted: function(s) {
  1499.     return !!this.untrustedSites.matches(s);
  1500.   },
  1501.   setUntrusted: function(s, b) {
  1502.     var change = b ? this.untrustedSites.add(s) : this.untrustedSites.remove(s, false, true);
  1503.     if (change) {
  1504.       this.persistUntrusted();
  1505.     }
  1506.     return b;
  1507.   },
  1508.   persistUntrusted: function(snapshot) {
  1509.     this.setPref("untrusted", snapshot || this.untrustedSites.sitesString);
  1510.   },
  1511.   
  1512.   manualSites: new PolicySites(),
  1513.   isManual: function(s) {
  1514.     return !!this.manualSites.matches(s);
  1515.   },
  1516.   setManual: function(s, b) {
  1517.     if (b) this.manualSites.add(s);
  1518.     else this.manualSites.remove(s, true);
  1519.     return b;
  1520.   },
  1521.   
  1522.   autoTemp: function(site) {
  1523.     if (!(this.isUntrusted(site) || this.isManual(site))) {
  1524.       this.setTemp(site, true);
  1525.       this.setJSEnabled(site, true);
  1526.       return true;
  1527.     }
  1528.     return false;
  1529.   }
  1530. ,
  1531.   mustCascadeTrust: function(sites, temp) {
  1532.     var untrustedGranularity = this.getPref("untrustedGranularity", 3);
  1533.     /*  noscript.untrustedGranularity  controls how manually whitelisting
  1534.         a domain affects the untrusted blacklist status of descendants:
  1535.         0 - always delist descendants from the untrusted blacklist
  1536.         1 - keep descendants blacklisted for temporary allow actions
  1537.         2 - keep descendants blacklisted for permanent allow actions
  1538.         3 - (default) always keep descendants blacklisted
  1539.         4 - delist blacklisted descendants of a site marked as untrusted
  1540.         All these values can be put in OR (the 3 default is actually 2 | 1)
  1541.     */
  1542.     var single = !(typeof(site) == "object" && ("push" in site)); // not an array
  1543.     return !((untrustedGranularity & 1) && !temp || (untrustedGranularity & 2) && temp)
  1544.       || (untrustedGranularity & 4) && single && this.isUntrusted(site);
  1545.   }
  1546. ,
  1547.   jsPolicySites: new PolicySites(),
  1548.   isJSEnabled: function(s) {
  1549.     return !(this.globalJS
  1550.       ? this.alwaysBlockUntrustedContent && this.untrustedSites.matches(s)
  1551.       : !this.jsPolicySites.matches(s) || this.untrustedSites.matches(s)
  1552.     );
  1553.   },
  1554.   setJSEnabled: function(site, is, fromScratch, cascadeTrust) {
  1555.     const ps = this.jsPolicySites;
  1556.     if (fromScratch) ps.sitesString = this.permanentSites.sitesString;
  1557.     if (is) {
  1558.       ps.add(site);
  1559.       if (!fromScratch) {
  1560.         if (this.untrustedSites.remove(site, false, !cascadeTrust)) 
  1561.           this.persistUntrusted();
  1562.         
  1563.         this.setManual(site, false);
  1564.       }
  1565.     } else {
  1566.       ps.remove(site, false, true);
  1567.       if (this.forbidImpliesUntrust) {
  1568.         this._lastSnapshot.add(site);
  1569.         this.setUntrusted(site, true);
  1570.       } else {
  1571.         this.setManual(site, true);
  1572.       }
  1573.     }
  1574.     this.flushCAPS();
  1575.     return is;
  1576.   },
  1577.   
  1578.   get forbidImpliesUntrust() {
  1579.     return this.globalJS || this.autoAllow || this.getPref("forbidImpliesUntrust", false);
  1580.   }
  1581.   
  1582. ,
  1583.   checkShorthands: function(site, map) {
  1584.     if (this.whitelistRegExp && this.whitelistRegExp.test(site)) {
  1585.       return true;
  1586.     }
  1587.     
  1588.     map = map || this.jsPolicySites.sitesMap;
  1589.     // port matching, with "0" as port wildcard  and * as nth level host wildcard
  1590.     if (/:\d+$/.test(site)) {
  1591.       var key = site.replace(/\d+$/, "0");
  1592.       if (map[key]) return true;
  1593.       var keys = key.split(".");
  1594.       if (keys.length > 1) {
  1595.         var prefix = keys[0].match(/^https?:\/\//i)[0] + "*.";
  1596.         while (keys.length > 2) {
  1597.           keys.shift();
  1598.           if (map[prefix + keys.join(".")]) return true;
  1599.         }
  1600.       }
  1601.     }
  1602.     // check IP leftmost portion up to 2nd byte (e.g. [http://]192.168 or [http://]10.0.0)
  1603.     var m = site.match(/^(https?:\/\/)((\d+\.\d+)\.\d+)\.\d+(?::\d|$)/);
  1604.     return m && (map[m[2]] || map[m[3]] || map[m[1] + m[2]] || map[m[1] + m[3]]);
  1605.   }
  1606. ,
  1607.   flushCAPS: function(sitesString) {
  1608.     const ps = this.jsPolicySites;
  1609.     if (sitesString) ps.sitesString = sitesString;
  1610.     
  1611.     // dump("Flushing " + ps.sitesString);
  1612.     ps.toPref(this.policyPB);
  1613.   }
  1614. ,
  1615.   get injectionChecker() {
  1616.     return InjectionChecker;
  1617.   }
  1618. ,
  1619.   splitList: function(s) {
  1620.     return s?/^[,\s]*$/.test(s)?[]:s.split(/\s*[,\s]\s*/):[];
  1621.   }
  1622. ,
  1623.   savePrefs: function() {
  1624.     return this.prefService.savePrefFile(null);
  1625.   }
  1626. ,
  1627.   sortedSiteSet: function(s) { return  SiteUtils.sortedSet(s); }
  1628. ,
  1629.   globalJS: false,
  1630.   get jsEnabled() {
  1631.     try {
  1632.       return this.mozJSEnabled && this.caps.getCharPref("default.javascript.enabled") != "noAccess";
  1633.     } catch(ex) {
  1634.       return this.uninstalling ? this.mozJSEnabled : (this.jsEnabled = this.globalJS);
  1635.     }
  1636.   }
  1637. ,
  1638.   set jsEnabled(enabled) {
  1639.     if (this.locked || this.prefs.prefIsLocked("global")) {
  1640.       enabled = false;
  1641.     }
  1642.     const prefName = "default.javascript.enabled";
  1643.     try {
  1644.       this.caps.clearUserPref("default.javascript.enabled");
  1645.     } catch(e) {}
  1646.     this.defaultCaps.setCharPref(prefName, enabled ? "allAccess" : "noAccess");
  1647.     this.setPref("global", enabled);
  1648.     if (enabled) {
  1649.       this.mozJSPref.setBoolPref("enabled", true);
  1650.     }
  1651.     return enabled;
  1652.   }
  1653. ,
  1654.   getSite: function(url) {
  1655.     return SiteUtils.getSite(url);
  1656.   },
  1657.   
  1658.   getQuickSite: function(url, level) {
  1659.     var site = null;
  1660.     if (level > 0 && !this.jsEnabled) {
  1661.       site = this.getSite(url);
  1662.       var domain;
  1663.       if (level > 1 && (domain = this.getDomain(site))) {
  1664.         site = level > 2 ? this.getBaseDomain(domain) : domain;
  1665.       }
  1666.     }
  1667.     return site;
  1668.   },
  1669.   
  1670.   get preferredSiteLevel() {
  1671.     return this.getPref("showAddress", false) ? 1 : this.getPref("showDomain", false) ? 2 : 3;
  1672.   },
  1673.   
  1674.   
  1675.   getDomain: function(site, force) {
  1676.     try {
  1677.       const url = (site instanceof CI.nsIURL) ? site : SiteUtils.ios.newURI(site, null, null);
  1678.       const host = url.host;
  1679.       return force || url.port == -1 && host[host.length - 1] != "." && 
  1680.             (host.lastIndexOf(".") > 0 || host == "localhost") ? host : null;
  1681.     } catch(e) {
  1682.       return null;
  1683.     }
  1684.   },
  1685.   
  1686.   _tldService: null,
  1687.   initTldService: function() {
  1688.       var srv = null;
  1689.       try {
  1690.         if (CI.nsIEffectiveTLDService) {
  1691.           var srv = CC["@mozilla.org/network/effective-tld-service;1"]
  1692.                   .getService(CI.nsIEffectiveTLDService);
  1693.           if (typeof(srv.getBaseDomainFromHost) == "function"
  1694.               && srv.getBaseDomainFromHost("bbc.co.uk") == "bbc.co.uk" // check, some implementations are "fake" (e.g. Songbird's)
  1695.             ) {
  1696.             return this._tldService = srv;
  1697.           }
  1698.         }
  1699.         CC["@mozilla.org/moz/jssubscript-loader;1"]
  1700.             .getService(CI["mozIJSSubScriptLoader"])
  1701.             .loadSubScript('chrome://noscript/content/tldEmulation.js');
  1702.         return this._tldService = EmulatedTLDService;
  1703.       } catch(ex) {
  1704.         this.dump(ex);
  1705.       }
  1706.       return null;
  1707.   },
  1708.  
  1709.   getBaseDomain: function(domain) {
  1710.     if (/^[\d\.]+$/.test(domain)) return domain; // IP
  1711.     
  1712.     var pos = domain.lastIndexOf('.');
  1713.     if (pos < 1 || (pos = domain.lastIndexOf('.', pos - 1)) < 1) return domain;
  1714.     
  1715.     try {
  1716.       return this._tldService.getBaseDomainFromHost(domain);
  1717.     } catch(e) {
  1718.       this.dump(e);
  1719.     }
  1720.     return domain;
  1721.   },
  1722.   getPublicSuffix: function(domain) {
  1723.     try {
  1724.       return this._tldService.getPublicSuffixFromHost(domain);
  1725.     } catch(e) {
  1726.       return "";
  1727.     }
  1728.   }
  1729. ,
  1730.  
  1731.   delayExec: function(callback, delay) {
  1732.      const timer = CC["@mozilla.org/timer;1"].createInstance(
  1733.         CI.nsITimer);
  1734.      var args = Array.prototype.slice.call(arguments, 2);
  1735.      timer.initWithCallback({ 
  1736.          notify: this.delayedRunner,
  1737.          context: { callback: callback, args: args, self: this }
  1738.       },  delay || 1, 0);
  1739.   },
  1740.   delayedRunner: function() {
  1741.     var ctx = this.context;
  1742.     try {
  1743.        ctx.callback.apply(ctx.self, ctx.args);
  1744.      } catch(e) {}
  1745.      finally {
  1746.        ctx.args = null;
  1747.        ctx.callback = null;
  1748.      }
  1749.   }
  1750. ,
  1751.   safeCapsOp: function(callback, reloadCurrentTabOnly) {
  1752.     this.delayExec(function() {
  1753.       callback();
  1754.       this.savePrefs();
  1755.       this.reloadWhereNeeded(reloadCurrentTabOnly);
  1756.      }, 1);
  1757.   }
  1758. ,
  1759.   _lastSnapshot: null,
  1760.   _lastGlobal: false,
  1761.   _lastObjects: null,
  1762.   reloadWhereNeeded: function(currentTabOnly) {
  1763.     var snapshot = this._lastSnapshot;
  1764.     const ps = this.jsPolicySites;
  1765.     this._lastSnapshot = ps.clone();
  1766.     const global = this.jsEnabled;
  1767.     var lastGlobal = this._lastGlobal;
  1768.     this._lastGlobal = global;
  1769.   
  1770.     var lastObjects = this._lastObjects || this.objectWhitelist;
  1771.     this._lastObjects = this.objectWhitelist;
  1772.     
  1773.     this.initContentPolicy();
  1774.     
  1775.     if (!snapshot ||
  1776.         global == lastGlobal && lastObjects == this.objectWhitelist && 
  1777.         ps.equals(snapshot)
  1778.         ) 
  1779.       return false;
  1780.    
  1781.     
  1782.     if (!this.getPref("autoReload", true)) return false;
  1783.     if (global != lastGlobal && !this.getPref("autoReload.global", true)) return false;
  1784.     
  1785.     currentTabOnly = currentTabOnly || !this.getPref("autoReload.allTabs", true);
  1786.     var useHistory = this.getPref("xss.reload.useHistory", false);
  1787.     var useHistoryExceptCurrent = this.getPref("xss.reload.useHistory.exceptCurrent", true);
  1788.     
  1789.     var ret = false;
  1790.     var docSites, site;
  1791.     var prevStatus, currStatus;
  1792.     
  1793.     var webNav, url;
  1794.     
  1795.     const nsIWebNavigation = CI.nsIWebNavigation;
  1796.     const nsIURL = CI.nsIURL;
  1797.     const LOAD_FLAGS = nsIWebNavigation.LOAD_FLAGS_NONE;
  1798.     const untrustedReload = !this.getPref("xss.trustReloads", false);
  1799.     
  1800.     var bi = new this.domUtils.createBrowserIterator();
  1801.     for (var browser, j; browser = bi.next();) {
  1802.       docSites = this.getSites(browser);
  1803.       for (j = docSites.length; j-- > 0;) {
  1804.         prevStatus = lastGlobal && !this.alwaysBlockUntrustedContent || !!snapshot.matches(docSites[j]);
  1805.         currStatus = this.isJSEnabled(docSites[j]) || !!this.checkShorthands(docSites[j]);
  1806.         if (currStatus != prevStatus) {
  1807.           ret = true;
  1808.           if (currStatus) 
  1809.             this.requestWatchdog.setUntrustedReloadInfo(browser, true);
  1810.           
  1811.           webNav = browser.webNavigation;
  1812.           url = webNav.currentURI;
  1813.           if (url.schemeIs("http") || url.schemeIs("https")) {
  1814.             this.requestWatchdog.noscriptReload = url.spec;
  1815.           }
  1816.           try {
  1817.             webNav = webNav.sessionHistory.QueryInterface(nsIWebNavigation);
  1818.             if (currStatus && webNav.index && untrustedReload) {
  1819.               try {
  1820.                 site = this.getSite(webNav.getEntryAtIndex(webNav.index - 1, false).URI.spec);
  1821.                 this.requestWatchdog.setUntrustedReloadInfo(browser, site != docSites[j] && !ps.matches(site));
  1822.               } catch(e) {}
  1823.             }
  1824.             
  1825.             if (useHistory) {
  1826.               if (useHistoryExceptCurrent) {
  1827.                 useHistoryExceptCurrent = false;
  1828.               } else if(!(url instanceof nsIURL && url.ref || url.spec.substring(url.spec.length - 1) == "#")) {
  1829.                 if (useHistoryCurrentOnly) useHistory = false;
  1830.                 webNav.gotoIndex(webNav.index);
  1831.                 break;
  1832.               }
  1833.             }
  1834.           } catch(e) {}
  1835.           browser.webNavigation.reload(LOAD_FLAGS);
  1836.           break;
  1837.         }
  1838.       }
  1839.       
  1840.       if(j < 0) { 
  1841.         // check plugin objects
  1842.         if (this.consoleDump & LOG_CONTENT_BLOCK) {
  1843.           this.dump("Checking object permission changes...");
  1844.           try {
  1845.             this.dump(docSites.toSource() + ", " + lastObjects.toSource());
  1846.           } catch(e) {}
  1847.         }
  1848.         if (this.checkObjectPermissionsChange(docSites, lastObjects)) {
  1849.            ret = true;
  1850.            this.quickReload(browser.webNavigation);
  1851.         }
  1852.       }
  1853.       
  1854.       if (currentTabOnly) break;
  1855.     }
  1856.     bi.dispose();
  1857.     bi = null;
  1858.     return ret;
  1859.   },
  1860.   
  1861.   checkObjectPermissionsChange: function(sites, snapshot) {
  1862.     if(this.objectWhitelist == snapshot) return false;
  1863.     var s, url;
  1864.     for (url in snapshot) {
  1865.       s = this.getSite(url);
  1866.       if (!(s in snapshot)) snapshot[s] = snapshot[url];
  1867.     }
  1868.     for each (var s in sites.pluginSites) {
  1869.       if ((s in snapshot) && !(s in this.objectWhitelist)) {
  1870.         return true;
  1871.       }
  1872.     }
  1873.     var egroup, e;
  1874.     for each (egroup in sites.pluginExtras) {
  1875.       for each (e in egroup) {
  1876.         if (!e.placeholder && (e.url in snapshot) && !(e.url in this.objectWhitelist)) {
  1877.            return true;
  1878.         }
  1879.       }
  1880.     }
  1881.     return false;
  1882.   },
  1883.   
  1884.   quickReload: function(webNav) {
  1885.     var uri = webNav.currentURI;
  1886.     if (uri.schemeIs("http") || uri.schemeIs("https")) {
  1887.       this.requestWatchdog.noscriptReload = uri.spec;
  1888.     }
  1889.     webNav.reload(webNav.LOAD_FLAGS_CHARSET_CHANGE);
  1890.   },
  1891.   
  1892.   eraseTemp: function() {
  1893.     // remove temporary PUNCTUALLY: 
  1894.     // keeps ancestors because the may be added as permanent after the temporary allow;
  1895.     // keeps descendants because they may already have been permanent before the temporary, and then shadowed
  1896.     this.jsPolicySites.remove(this.tempSites.sitesList, true, true);
  1897.     // if in blacklist mode, put back temporarily allowed in blacklist
  1898.     if (this.globalJS && this.alwaysBlockUntrustedContent &&
  1899.         this.untrustedSites.add(this.tempSites.sitesList)) {
  1900.       this.persistUntrusted();
  1901.     }
  1902.  
  1903.     this.setJSEnabled(this.permanentSites.sitesList, true); // add permanent & save
  1904.     this.setPref("temp", ""); // flush temporary list
  1905.     this.resetAllowedObjects();
  1906.   }
  1907. ,
  1908.   _observingPolicies: false,
  1909.   _editingPolicies: false,
  1910.   setupJSCaps: function() {
  1911.     if (this._editingPolicies) return;
  1912.     this._editingPolicies = true;
  1913.     try {
  1914.       const POLICY_NAME = this.POLICY_NAME;
  1915.       var prefArray;
  1916.       var prefString = "", originalPrefString = "";
  1917.       var exclusive = this.getPref("excaps", true);
  1918.       try {
  1919.         
  1920.         prefArray = this.splitList(prefString = originalPrefString = 
  1921.           (this.caps.prefHasUserValue("policynames") 
  1922.             ? this.caps.getCharPref("policynames")
  1923.             : this.getPref("policynames") // saved value from dirty exit
  1924.           )
  1925.         );
  1926.         var pcount = prefArray.length;
  1927.         while (pcount-- > 0 && prefArray[pcount] != POLICY_NAME);
  1928.         if (pcount == -1) { // our policy is not installed, should always be so unless dirty exit
  1929.           this.setPref("policynames", originalPrefString);
  1930.           if (exclusive || prefArray.length == 0) {
  1931.             prefString = POLICY_NAME;
  1932.           } else {
  1933.             prefArray.push(POLICY_NAME);
  1934.             prefString = prefArray.join(' ');
  1935.           }
  1936.         }
  1937.         prefString = prefString.replace(/,/g, ' ').replace(/\s+/g, ' ').replace(/^\s+/, '').replace(/\s+$/, '');
  1938.       } catch(ex) {
  1939.         prefString = POLICY_NAME;
  1940.       }
  1941.       
  1942.       this.caps.setCharPref(POLICY_NAME + ".javascript.enabled", "allAccess");
  1943.       
  1944.       try {
  1945.         this.caps.clearUserPref("policynames");
  1946.       } catch(e) {}
  1947.       this.defaultCaps.setCharPref("policynames", prefString);
  1948.  
  1949.       
  1950.       if (!this._observingPolicies) {
  1951.         this.caps.addObserver("policynames", this, true);
  1952.         this._observingPolicies = true;
  1953.       }
  1954.     } catch(ex) {
  1955.       dump(ex.message);
  1956.     }
  1957.     this._editingPolicies = false;
  1958.   },
  1959.   resetJSCaps: function() {
  1960.     try {
  1961.       this.caps.clearUserPref("default.javascript.enabled");
  1962.     } catch(ex) {}
  1963.     if (this._observingPolicies) {
  1964.       this.caps.removeObserver("policynames", this, false);
  1965.       this._observingPolicies = false;
  1966.     }
  1967.     try {
  1968.       const POLICY_NAME = this.POLICY_NAME;
  1969.       var prefArray = SiteUtils.splitString(
  1970.         this.getPref("excaps", true) ? this.getPref("policynames", "") : this.caps.getCharPref("policynames")
  1971.       );
  1972.       var pcount = prefArray.length;
  1973.       const prefArrayTarget = [];
  1974.       for (var pcount = prefArray.length; pcount-- > 0;) {
  1975.         if (prefArray[pcount] != POLICY_NAME) prefArrayTarget[prefArrayTarget.length] = prefArray[pcount];
  1976.       }
  1977.       var prefString = prefArrayTarget.join(" ").replace(/\s+/g,' ').replace(/^\s+/,'').replace(/\s+$/,'');
  1978.       if (prefString) {
  1979.         this.caps.setCharPref("policynames", prefString);
  1980.       } else {
  1981.         try {
  1982.           this.caps.clearUserPref("policynames");
  1983.         } catch(ex1) {}
  1984.       }
  1985.       try {
  1986.         this.clearUserPref("policynames");
  1987.       } catch(ex1) {}
  1988.       this.eraseTemp();
  1989.       this.savePrefs();
  1990.     } catch(ex) {}
  1991.   }
  1992. ,
  1993.   uninstallJob: function() {
  1994.     // this.resetJSCaps();
  1995.   },
  1996.   undoUninstallJob: function() {
  1997.     // this.setupJSCaps();
  1998.   }
  1999. ,
  2000.   getPref: function(name, def) {
  2001.     const IPC = CI.nsIPrefBranch;
  2002.     const prefs = this.prefs;
  2003.     try {
  2004.       switch (prefs.getPrefType(name)) {
  2005.         case IPC.PREF_STRING:
  2006.           return prefs.getCharPref(name);
  2007.         case IPC.PREF_INT:
  2008.           return prefs.getIntPref(name);
  2009.         case IPC.PREF_BOOL:
  2010.           return prefs.getBoolPref(name);
  2011.       }
  2012.     } catch(e) {}
  2013.     return def || "";
  2014.   }
  2015. ,
  2016.   setPref: function(name, value) {
  2017.     const prefs = this.prefs;
  2018.     switch (typeof(value)) {
  2019.       case "string":
  2020.           prefs.setCharPref(name,value);
  2021.           break;
  2022.       case "boolean":
  2023.         prefs.setBoolPref(name,value);
  2024.         break;
  2025.       case "number":
  2026.         prefs.setIntPref(name,value);
  2027.         break;
  2028.       default:
  2029.         throw new Error("Unsupported type "+typeof(value)+" for preference "+name);
  2030.     }
  2031.   }
  2032. ,
  2033.   _sound: null,
  2034.   playSound: function(url, force) {
  2035.     if (force || this.getPref("sound", false)) {
  2036.       var sound = this._sound;
  2037.       if (sound == null) {
  2038.         sound = CC["@mozilla.org/sound;1"].createInstance(CI.nsISound);
  2039.         sound.init();
  2040.         this._sound = sound;
  2041.       }
  2042.       try {
  2043.         sound.play(SiteUtils.ios.newURI(url, null, null));
  2044.       } catch(ex) {
  2045.         //dump(ex);
  2046.       }
  2047.     }
  2048.   },
  2049.   _soundNotified: {},
  2050.   soundNotify: function(url) {
  2051.     if (this.getPref("sound.oncePerSite", true)) {
  2052.       const site = this.getSite(url);
  2053.       if (this._soundNotified[site]) return;
  2054.       this._soundNotified[site] = true;
  2055.     }
  2056.     this.playSound(this.getPref("sound.block"));
  2057.   }
  2058. ,
  2059.   readFile: function(file) {
  2060.     const is = CC["@mozilla.org/network/file-input-stream;1"].createInstance(
  2061.           CI.nsIFileInputStream );
  2062.     is.init(file ,0x01, 0400, null);
  2063.     const sis = CC["@mozilla.org/scriptableinputstream;1"].createInstance(
  2064.       CI.nsIScriptableInputStream );
  2065.     sis.init(is);
  2066.     const res=sis.read(sis.available());
  2067.     is.close();
  2068.     return res;
  2069.   }
  2070. ,
  2071.   writeFile: function(file, content) {
  2072.     const unicodeConverter = CC["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(
  2073.     CI.nsIScriptableUnicodeConverter);
  2074.     unicodeConverter.charset = "UTF-8";
  2075.     content=unicodeConverter.ConvertFromUnicode(content);
  2076.     const os=CC["@mozilla.org/network/file-output-stream;1"].createInstance(
  2077.       CI.nsIFileOutputStream);
  2078.     os.init(file, 0x02 | 0x08 | 0x20,0664,0);
  2079.     os.write(content,content.length);
  2080.     os.close();
  2081.   }
  2082. ,
  2083.   
  2084.   getAllowObjectMessage: function(url, mime) {
  2085.     url = this.siteUtils.crop(url);
  2086.     return this.getString("allowTemp", [url + "\n(" + mime + ")\n"]);
  2087.   }
  2088. ,
  2089.   lookupMethod: DOMUtils.lookupMethod,
  2090.   domUtils: DOMUtils,
  2091.   siteUtils: SiteUtils,
  2092.   uriValidator: URIValidator
  2093. ,
  2094.  
  2095.   
  2096.   mimeService: null,
  2097.   xcache: null,
  2098.  
  2099.   shouldLoad: CP_NOP,
  2100.   shouldProcess: CP_NOP,
  2101.   initContentPolicy: function() {
  2102.     var delegate = this.disabled || (this.globalJS && !this.alwaysBlockUntrustedContent)   
  2103.       ? this.noopContentPolicy
  2104.       : this.mainContentPolicy;
  2105.     this.shouldLoad = delegate.shouldLoad;
  2106.     this.shouldProcess = delegate.shouldProcess;
  2107.  
  2108.     if (!this.mimeService) {
  2109.       
  2110.       this.rejectCode = typeof(/ /) == "object" ? -4 : -3;
  2111.       this.safeToplevel = this.getPref("safeToplevel", true);
  2112.       this.initSafeJSRx();
  2113.       this.mimeService = CC['@mozilla.org/uriloader/external-helper-app-service;1']
  2114.                                    .getService(CI.nsIMIMEService);
  2115.     }
  2116.   },
  2117.  
  2118.   
  2119.   guessMime: function(uri) {
  2120.     try {
  2121.       var ext =  (uri instanceof CI.nsIURL) && uri.fileExtension;
  2122.       return ext && this.mimeService.getTypeFromExtension(ext) || "";
  2123.     } catch(e) {
  2124.       return "";
  2125.     }
  2126.   },
  2127.   pluginForMime: function(mimeType) {
  2128.     if (!mimeType) return null;
  2129.     try {
  2130.       var w = DOMUtils.mostRecentBrowserWindow;
  2131.       if (!(w && w.navigator)) return null;
  2132.       var mime = w.navigator.mimeTypes.namedItem(mimeType);
  2133.       return mime && mime.enabledPlugin || null;
  2134.     } catch(e) { return null; }
  2135.   },
  2136.   
  2137.   browserChromeDir: CC["@mozilla.org/file/directory_service;1"].getService(CI.nsIProperties)
  2138.                        .get("AChrom", CI.nsIFile),
  2139.   chromeRegistry: CC["@mozilla.org/chrome/chrome-registry;1"].getService(CI.nsIChromeRegistry),
  2140.   checkForbiddenChrome: function(url, origin) {
  2141.     if(origin && !/^(?:chrome|resource|about)$/.test(origin.scheme)) {
  2142.       switch(url.scheme) {
  2143.         case "chrome":
  2144.           var packageName = url.host;
  2145.           if (packageName == "browser") return false; // fast path for commonest case
  2146.           exception = this.getPref("forbidChromeExceptions." + packageName, false);
  2147.           if (exception) return false;
  2148.           var chromeURL = this.chromeRegistry.convertChromeURL(url);
  2149.           if (chromeURL instanceof CI.nsIJARURI) 
  2150.             chromeURL = chromeURL.JARFile;
  2151.                 
  2152.           return chromeURL instanceof CI.nsIFileURL && !this.browserChromeDir.contains(chromeURL.file, true);
  2153.          
  2154.         case "resource":
  2155.           if(/\.\./.test(unescape(url.spec))) return true;
  2156.       }
  2157.     }
  2158.     return false;
  2159.   },
  2160.   // nsIContentPolicy interface
  2161.   // we use numeric constants for performance sake: 
  2162.   // nsIContentPolicy.TYPE_OTHER = 1
  2163.   // nsIContentPolicy.TYPE_SCRIPT = 2
  2164.   // nsIContentPolicy.TYPE_IMAGE = 3
  2165.   // nsIContentPolicy.TYPE_OBJECT = 5
  2166.   // nsIContentPolicy.TYPE_DOCUMENT = 6
  2167.   // nsIContentPolicy.TYPE_SUBDOCUMENT = 7
  2168.   // nsIContentPolicy.TYPE_REFRESH = 8
  2169.   // nsIContentPolicy.TYPE_XBL = 9
  2170.   // nsIContentPolicy.TYPE_PING = 10
  2171.   // nsIContentPolicy.TYPE_XMLHTTPREQUEST = 11
  2172.   // nsIContentPolicy.TYPE_OBJECT_SUBREQUEST = 12
  2173.   // nsIContentPolicy.REJECT_SERVER = -3
  2174.   // nsIContentPolicy.ACCEPT = 1
  2175.   POLICY1_9: "TYPE_XBL" in CI.nsIContentPolicy,
  2176.   noopContentPolicy: {
  2177.     shouldLoad: CP_NOP,
  2178.     shouldProcess: CP_NOP
  2179.   },
  2180.   cpConsoleFilter: [2, 5, 6, 7],
  2181.   cpDump: function(msg, aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aInternalCall) {
  2182.     this.dump("Content " + msg + " -- type: " + aContentType + ", location: " + (aContentLocation && aContentLocation.spec) + 
  2183.       ", origin: " + (aRequestOrigin && aRequestOrigin.spec) + ", ctx: " + 
  2184.         ((aContext instanceof CI.nsIDOMHTMLElement) ? "<HTML Element>" // try not to cause side effects of toString() during load
  2185.           : aContext)  + 
  2186.         ", mime: " + aMimeTypeGuess + ", " + aInternalCall);
  2187.   },
  2188.   reject: function(what, args) {
  2189.     this.resetPolicyState();
  2190.     if (this.consoleDump) {
  2191.       if(this.consoleDump & LOG_CONTENT_BLOCK && args.length == 6) {
  2192.         this.cpDump("BLOCKED " + what, args[0], args[1], args[2], args[3], args[4], args[5]);
  2193.       }
  2194.       if(this.consoleDump & LOG_CONTENT_CALL) {
  2195.         this.dump(new Error().stack);
  2196.       }
  2197.     }
  2198.     switch(args[0]) {
  2199.       case 6: case 7: 
  2200.         this.xcache.pickOrigin(args[1], true); 
  2201.         break;
  2202.       case 9:
  2203.         // our take on https://bugzilla.mozilla.org/show_bug.cgi?id=387971
  2204.         args[1].spec = this.nopXBL;
  2205.         return CP_OK;
  2206.     }
  2207.     return this.rejectCode;
  2208.   },
  2209.   
  2210.   get nopXBL() {
  2211.     var url = this.POLICY1_9
  2212.       ? "chrome://global/content/bindings/general.xml#basecontrol"
  2213.       : this.contentBase + "noscript.xbl#nop";
  2214.     
  2215.     this.__defineGetter__("nopXBL", function() { return url });
  2216.     return url;
  2217.   },
  2218.   
  2219.   mainContentPolicy: {
  2220.     shouldLoad: function(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aInternalCall) {
  2221.       
  2222.       var originURL, locationURL, originSite, locationSite, scheme,
  2223.           forbid, isJS, isJava, isFlash, isSilverlight,
  2224.           isLegacyFrame, blockThisIFrame, contentDocument,
  2225.           logIntercept, logBlock;
  2226.       
  2227.       logIntercept = this.consoleDump;
  2228.       if(logIntercept) {
  2229.         logBlock = logIntercept & LOG_CONTENT_BLOCK;
  2230.         logIntercept = logIntercept & LOG_CONTENT_INTERCEPT;
  2231.       } else logBlock = false;
  2232.       
  2233.       try {
  2234.         if (aContentType == 1 && !this.POLICY1_9) { // compatibility for type OTHER
  2235.           if (aContext instanceof CI.nsIDOMHTMLDocument) {
  2236.             aContentType = arguments.callee.caller ? 11 : 9;
  2237.           } else if ((aContext instanceof CI.nsIDOMHTMLElement)) {
  2238.             if ((aContext instanceof CI.nsIDOMHTMLEmbedElement || aContext instanceof CI.nsIDOMHTMLObjectElement)) {
  2239.               aContentType = 12;
  2240.             } else if (aContext.getAttribute("ping")) {
  2241.               aContentType = 10;
  2242.             }
  2243.           }
  2244.           arguments[0] = aContentType;
  2245.         }
  2246.         
  2247.         if (logIntercept && this.cpConsoleFilter.indexOf(aContentType) > -1) {
  2248.           this.cpDump("processing", aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aInternalCall);
  2249.           if (this.consoleDump & LOG_CONTENT_CALL)
  2250.              this.dump(new Error().stack);
  2251.         }
  2252.  
  2253.         this.currentPolicyURI = aContentLocation;
  2254.         this.currentPolicyHints = arguments;
  2255.         
  2256.         switch (aContentType) {
  2257.           case 9: // XBL - warning, in 1.8.x could also be XMLHttpRequest...
  2258.             return this.forbidXBL && 
  2259.               this.forbiddenXMLRequest(aRequestOrigin, aContentLocation, aContext, this.forbiddenXBLContext) 
  2260.               ? this.reject("XBL", arguments) : CP_OK;
  2261.           
  2262.           case 11: // in Firefox 3 we check for cross-site XHR
  2263.             return this.forbidXHR && 
  2264.               this.forbiddenXMLRequest(aRequestOrigin, aContentLocation, aContext, this.forbiddenXHRContext) 
  2265.                ? this.reject("XHR", arguments) : CP_OK;
  2266.           
  2267.           case 10: // TYPE_PING
  2268.             if (this.jsEnabled || !this.getPref("noping", true) || 
  2269.                 aRequestOrigin && this.isJSEnabled(this.getSite(aRequestOrigin.spec))
  2270.               )
  2271.               return CP_OK;
  2272.               
  2273.             return this.reject("Ping", arguments);
  2274.               
  2275.           case 2:
  2276.             if (this.forbidChromeScripts && this.checkForbiddenChrome(aContentLocation, aRequestOrigin)) {
  2277.               return this.reject("Chrome Access", arguments);
  2278.             }
  2279.             forbid = isJS = true;
  2280.             break;
  2281.           case 3: // IMAGES
  2282.             if (this.blockNSWB && (aContext instanceof CI.nsIDOMHTMLImageElement)) {
  2283.               try {
  2284.                 for (var parent = aContext; (parent = parent.parentNode);) {
  2285.                   if (parent.nodeName.toUpperCase() == "NOSCRIPT")
  2286.                     return this.reject("Tracking Image", arguments);
  2287.                 }
  2288.               } catch(e) {
  2289.                 this.dump(e)
  2290.               }
  2291.             }
  2292.  
  2293.             this.resetPolicyState();
  2294.             return CP_OK;
  2295.  
  2296.           case 5:
  2297.             if (aContentLocation && aRequestOrigin && 
  2298.                 (locationURL = aContentLocation.spec) == (originURL = aRequestOrigin.spec) && 
  2299.                 (aContext instanceof CI.nsIDOMHTMLEmbedElement) &&
  2300.                 aMimeTypeGuess && 
  2301.                 this.isAllowedObject(locationURL, aMimeTypeGuess)
  2302.                 ) {
  2303.               if (logIntercept) this.dump("Plugin document " + locationURL);
  2304.               return CP_OK; // plugin document, we'll handle it in our webprogress listener
  2305.             }
  2306.             
  2307.             if (this.checkJarDocument(aContentLocation, aContext)) 
  2308.               return this.reject("Plugin content from JAR", arguments);
  2309.             
  2310.             break;
  2311.             
  2312.           case 7:
  2313.             locationURL = aContentLocation.spec;
  2314.             originURL = aRequestOrigin && aRequestOrigin.spec;
  2315.             if (locationURL == "about:blank" || /^chrome:/.test(locationURL)
  2316.               || !originURL && (aContext instanceof CI.nsIDOMXULElement)  // custom browser like in Stumbleupon discovery window
  2317.             ) return CP_OK;
  2318.             
  2319.             if (!aMimeTypeGuess) {
  2320.               aMimeTypeGuess = this.guessMime(aContentLocation);
  2321.               if (logIntercept)
  2322.                 this.dump("Guessed MIME '" + aMimeTypeGuess + "' for location " + locationURL);
  2323.             }
  2324.             
  2325.             isLegacyFrame = aContext instanceof CI.nsIDOMHTMLFrameElement;
  2326.             
  2327.             if(this.forbidIFrames && !isLegacyFrame) {
  2328.               try {
  2329.                 contentDocument = aContext.contentDocument;
  2330.               } catch(e) {}
  2331.            
  2332.               blockThisIFrame = !(aInternalCall || 
  2333.                       /^(?:chrome|resource|wyciwyg):/.test(locationURL) ||
  2334.                       locationURL == this._silverlightInstalledHack ||
  2335.                       (
  2336.                         originURL
  2337.                           ? (/^chrome:/.test(originURL) ||
  2338.                              /^(?:data|javascript):/.test(locationURL) &&
  2339.                              (contentDocument && originURL == contentDocument.URL ||
  2340.                               this.isFirebugJSURL(locationURL)
  2341.                              )
  2342.                             )
  2343.                           : contentDocument && 
  2344.                             this.getSite(contentDocument.URL) == (locationSite = this.getSite(locationURL))
  2345.                        )
  2346.                   ) && this.forbiddenIFrameContext(originURL || (originURL = aContext.ownerDocument.URL), locationURL);
  2347.             }
  2348.           case 6:
  2349.             
  2350.             if (this.checkJarDocument(aContentLocation, aContext)) 
  2351.               return this.reject("JAR Document", arguments);
  2352.             
  2353.             scheme = aContentLocation.scheme;
  2354.             
  2355.             if (aRequestOrigin && aRequestOrigin != aContentLocation) {
  2356.               
  2357.               if (this.safeToplevel && (aContext instanceof CI.nsIDOMChromeWindow) &&
  2358.                   aContext.isNewToplevel &&
  2359.                   !(/^(?:chrome|resource|file)$/.test(scheme) ||
  2360.                     this.isSafeJSURL(aContentLocation.spec))
  2361.                     ) {
  2362.                 return this.reject("Top Level Window Loading", arguments);
  2363.               }
  2364.            
  2365.               if (/^https?$/.test(scheme)) {
  2366.                 if (aRequestOrigin.prePath != aContentLocation.prePath) {
  2367.                   if (aRequestOrigin.schemeIs("chrome") && aContext && aContext.ownerDocument &&
  2368.                     aContext.ownerDocument.defaultView.isNewToplevel){
  2369.                     this.requestWatchdog.externalLoad = aContentLocation.spec;
  2370.                   }
  2371.                   this.xcache.storeOrigin(aRequestOrigin, aContentLocation);
  2372.                 }
  2373.               } else if(/^(?:data|javascript)$/.test(scheme)) {
  2374.                 //data: and javascript: URLs
  2375.                 locationURL = locationURL || aContentLocation.spec;
  2376.                 if (!this.isSafeJSURL(locationURL) &&
  2377.                   ((this.forbidData && !this.isFirebugJSURL(locationURL) || locationURL == "javascript:") && 
  2378.                     !this.isJSEnabled(originSite = this.getSite(originURL = originURL || aRequestOrigin.spec)) ||
  2379.                     aContext && (
  2380.                       (aContext instanceof CI.nsIDOMWindow) 
  2381.                         ? aContext
  2382.                         : aContext.ownerDocument.defaultView
  2383.                     ).isNewToplevel
  2384.                   )
  2385.                  ) {
  2386.                    return this.reject("JavaScript/Data URL", arguments);
  2387.                 }
  2388.               } else if(scheme != aRequestOrigin.scheme && 
  2389.                   scheme != "chrome" && // faster path for common case
  2390.                   this.isExternalScheme(scheme)) {
  2391.                 // work-around for bugs 389106 & 389580, escape external protocols
  2392.                 if (aContentType != 6 && !aInternalCall && 
  2393.                     this.getPref("forbidExtProtSubdocs", true) && 
  2394.                     !this.isJSEnabled(originSite = this.getSite(originURL = originURL || aRequestOrigin.spec))) {
  2395.                   return this.reject("External Protocol Subdocument", arguments);
  2396.                 }
  2397.                 if (!this.normalizeExternalURI(aContentLocation)) {
  2398.                   return this.reject("Invalid External URL", arguments);
  2399.                 }
  2400.               } else if(aContentType == 6 && scheme == "chrome" &&
  2401.                 this.getPref("lockPrivilegedUI", false) && // block DOMI && Error Console
  2402.                 /^(?:javascript:|chrome:\/\/(?:global\/content\/console|inspector\/content\/inspector|venkman\/content\/venkman)\.xul)$/
  2403.                   .test(locationURL)) {
  2404.                 return this.reject("Locked Privileged UI", arguments);
  2405.               }
  2406.             }
  2407.             
  2408.             if (!(this.forbidSomeContent || this.alwaysBlockUntrustedContent) ||
  2409.                   !blockThisIFrame && (
  2410.                     !aMimeTypeGuess ||
  2411.                     aMimeTypeGuess.substring(0, 5) == "text/"
  2412.                     || aMimeTypeGuess == "application/xml" 
  2413.                     || aMimeTypeGuess == "application/xhtml+xml"
  2414.                     || aMimeTypeGuess.substring(0, 6) == "image/"
  2415.                     || !this.pluginForMime(aMimeTypeGuess)
  2416.                   )
  2417.               ) {
  2418.             
  2419.               if (logBlock)
  2420.                 this.dump("Document OK: " + aMimeTypeGuess + "@" + (locationURL || aContentLocation.spec) + 
  2421.                   " --- PGFM: " + this.pluginForMime(aMimeTypeGuess));
  2422.  
  2423.               return CP_OK;
  2424.             }
  2425.             break;
  2426.           
  2427.             
  2428.           case 12:
  2429.             // Silverlight mindless activation scheme :(
  2430.             if (!this.forbidSilverlight 
  2431.                 || !this.getExpando(aContext, "silverlight") || this.getExpando(aContext, "allowed"))
  2432.               return CP_OK;
  2433.  
  2434.             aMimeTypeGuess = "application/x-silverlight";
  2435.             break;
  2436.           default:
  2437.             return CP_OK;
  2438.         }
  2439.         
  2440.  
  2441.         locationURL = locationURL || aContentLocation.spec;
  2442.         locationSite = locationSite || this.getSite(locationURL);
  2443.         var untrusted = this.isUntrusted(locationSite);
  2444.         
  2445.         
  2446.         if(logBlock)
  2447.           this.dump("[CP PASS 2] " + aMimeTypeGuess + "*" + locationURL);
  2448.  
  2449.         if (isJS) {
  2450.           originSite = aRequestOrigin && this.getSite(aRequestOrigin.spec);
  2451.           
  2452.           // Silverlight hack
  2453.           
  2454.           if (this.contentBlocker && this.forbidSilverlight && this.silverlightPatch &&
  2455.                 originSite && /^(?:https?|file):/.test(originSite)) {
  2456.             this.applySilverlightPatch(aContext.ownerDocument);
  2457.           }
  2458.           
  2459.           if (originSite && locationSite == originSite) return CP_OK;
  2460.           
  2461.           this.getExpando(aContext.ownerDocument.defaultView.top, "codeSites", []).push(locationSite);
  2462.           
  2463.           return this.isJSEnabled(locationSite) || aContentLocation.scheme == "data" 
  2464.             ? CP_OK : this.reject("Script", arguments);
  2465.         }
  2466.  
  2467.         if (!(forbid || locationSite == "chrome:")) {
  2468.           var mimeKey = aMimeTypeGuess || "application/x-unknown"; 
  2469.           
  2470.           forbid = blockThisIFrame || untrusted && this.alwaysBlockUntrustedContent;
  2471.           if (!forbid && this.forbidSomeContent) {
  2472.             if (aMimeTypeGuess && !(this.allowedMimeRegExp && this.allowedMimeRegExp.test(aMimeTypeGuess))) {
  2473.               forbid = 
  2474.                 (
  2475.                   (isFlash = /^application\/(?:x-shockwave-flash|futuresplash)/i.test(aMimeTypeGuess)) ||
  2476.                   (isJava = /^application\/x-java\b/i.test(aMimeTypeGuess)) || 
  2477.                   (isSilverlight = /^application\/x-silverlight\b/i.test(aMimeTypeGuess)) 
  2478.                 ) &&
  2479.                 isFlash && this.forbidFlash || 
  2480.                 isJava && this.forbidJava || 
  2481.                 isSilverlight && this.forbidSilverlight;
  2482.               
  2483.               // see http://heasman.blogspot.com/2008/03/defeating-same-origin-policy-part-i.html
  2484.               if (isJava && /(?:[^\/\w\.\$]|^\s*\/\/)/.test(aContext.getAttribute("code") || "")) {
  2485.                 return this.reject("Illegal Java code attribute " + aContext.getAttribute("code"), arguments);
  2486.               }
  2487.               
  2488.               if (forbid) {
  2489.                 if (isSilverlight) {
  2490.                   forbid = aContentLocation != aRequestOrigin || aContext.firstChild;
  2491.                   if(forbid && aContentType != 12) {
  2492.                     this.setExpando(aContext, "silverlight", true);
  2493.                     if (!aContentLocation.spec == "data:,") {
  2494.                       try {
  2495.                         aContentLocation.spec = "data:,"; // normalize URL
  2496.                       } catch(normEx) {
  2497.                         if (this.consoleDump) this.dump("Couldn't normalize " + aContentLocation.spec + " to data:, - " + normEx);
  2498.                       }
  2499.                     }
  2500.                     locationURL = this.resolveSilverlightURL(aRequestOrigin, aContext);
  2501.                     locationSite = this.getSite(locationURL);
  2502.                   }
  2503.                 } else if (isFlash) {
  2504.                   locationURL = this.addFlashVars(locationURL, aContext);
  2505.                 }
  2506.               } else {
  2507.                 forbid = this.forbidPlugins && !(isJava || isFlash || isSilverlight);
  2508.                 if (forbid) {
  2509.                   locationURL = this.addObjectParams(locationURL, aContext);
  2510.                 }
  2511.               }
  2512.             }
  2513.           }
  2514.         }
  2515.         
  2516.         if(forbid && !this.contentBlocker) {
  2517.           
  2518.           originURL = originURL || (aRequestOrigin && aRequestOrigin.spec);
  2519.           originSite = originSite || this.getSite(originURL);
  2520.         
  2521.           var originOK = originSite 
  2522.             ? this.isJSEnabled(originSite) 
  2523.             : /^(?:javascript|data):/.test(originURL); // if we've got such an origin, parent should be trusted
  2524.           
  2525.           var locationOK = locationSite 
  2526.                 ? this.isJSEnabled(locationSite) 
  2527.                 : // use origin for javascript: or data:
  2528.                   /^(?:javascript|data):/.test(locationURL) && originOK
  2529.           ;
  2530.  
  2531.           forbid = !(locationOK && (originOK || 
  2532.             !this.getPref(blockThisIFrame 
  2533.             ? "forbidIFramesParentTrustCheck" : "forbidActiveContentParentTrustCheck", true)
  2534.             ));
  2535.         }
  2536.  
  2537.         this.delayExec(this.countObject, 0, aContext, locationSite);
  2538.  
  2539.         if(forbid) {
  2540.           try {  // moved here because of http://forums.mozillazine.org/viewtopic.php?p=3173367#3173367
  2541.             if (this.getExpando(aContext, "allowed") || 
  2542.               this.isAllowedObject(locationURL, mimeKey, locationSite)) {
  2543.               this.setExpando(aContext, "allowed", true);
  2544.               return CP_OK; // forceAllow
  2545.             }
  2546.           } catch(ex) {
  2547.             this.dump("Error checking plugin per-object permissions:" + ex);
  2548.           }
  2549.           
  2550.           if(isLegacyFrame) { // inject an embed and defer to load
  2551.               this.blockLegacyFrame(aContext, aContentLocation, aInternalCall);
  2552.               if (!aInternalCall) return CP_OK; 
  2553.           } else {
  2554.             try {
  2555.               if (aContext && (aContentType == 5 || aContentType == 7 || aContentType == 12)) {
  2556.                 if (aContext instanceof CI.nsIDOMNode) {
  2557.                   this.delayExec(this.tagForReplacement, 0, aContext, {
  2558.                     url: locationURL,
  2559.                     mime: mimeKey
  2560.                   });
  2561.                 }
  2562.               }
  2563.             } catch(ex) {
  2564.               if(this.consoleDump) this.dump(ex);
  2565.             } finally {
  2566.               return this.reject("Forbidden " + (contentDocument ? ("IFrame " + contentDocument.URL) : "Content"), arguments);
  2567.             }
  2568.           }
  2569.         } else {
  2570.           if(isSilverlight) {
  2571.             this.setExpando(aContext, "silverlight", aContentType != 12);
  2572.           }
  2573.           if(this.consoleDump & LOG_CONTENT_CALL) {
  2574.              this.dump(locationURL + " Allowed, " + new Error().stack);
  2575.           }
  2576.         }
  2577.       } catch(e) {
  2578.         return this.reject("Content (Fatal Error, " + e  + " - " + e.stack + ")", arguments);
  2579.       }
  2580.       return CP_OK;
  2581.     },
  2582.     shouldProcess: function(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeType, aExtra) {
  2583.       return this.shouldLoad(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeType, true);
  2584.     },
  2585.     check: function() {
  2586.       return false;
  2587.     }
  2588.   },
  2589.   
  2590.   forbiddenXMLRequest: function(aRequestOrigin, aContentLocation, aContext, forbidDelegate) {
  2591.     var originURL, locationURL;
  2592.     if (aContentLocation.schemeIs("chrome") || !aRequestOrigin || 
  2593.          // GreaseMonkey Ajax comes from resource: hidden window
  2594.          // Google Toolbar Ajax from about:blank
  2595.            /^(?:chrome:|resource:|about:blank)/.test(originURL = aRequestOrigin.spec) ||
  2596.            // Web Developer extension "appears" to XHR towards about:blank
  2597.            (locationURL = aContentLocation.spec) == "about:blank"
  2598.           ) return false;
  2599.     var win = aContext.defaultView;
  2600.     if(win) {
  2601.       this.getExpando(win.top, "codeSites", []).push(this.getSite(locationURL));
  2602.     }
  2603.     return forbidDelegate.call(this, originURL, locationURL);
  2604.   },
  2605.   
  2606.   addFlashVars: function(url, embed) {
  2607.     // add flashvars to have a better URL ID
  2608.     if (embed instanceof CI.nsIDOMElement) try {
  2609.       var flashvars = embed.getAttribute("flashvars");
  2610.       if (flashvars) url += "#!flashvars#" + encodeURI(flashvars); 
  2611.     } catch(e) {
  2612.       if (this.consoleDump) this.dump("Couldn't add flashvars to " + url + ":" + e);
  2613.     }
  2614.     return url;
  2615.   },
  2616.   
  2617.   addObjectParams: function(url, embed) {
  2618.     if (embed instanceof CI.nsIDOMElement) try {
  2619.       var params = embed.getElementsByTagName("param");
  2620.       if(!params.length) return url;
  2621.       
  2622.       var pp = [];
  2623.       for(var j = params.length; j-- > 0;) {
  2624.         pp.push(encodeURIComponent(params[j].name) + "=" + encodeURIComponent(params[j].value));
  2625.       }
  2626.       url += "#!objparams#" + pp.join("&");
  2627.     } catch(e) {
  2628.       if (this.consoleDump) this.dump("Couldn't add object params to " + url + ":" + e);
  2629.     }
  2630.     return url;
  2631.   },
  2632.   
  2633.   resolveSilverlightURL: function(uri, embed) {
  2634.     if(!uri) return "";
  2635.     var url = "";
  2636.     
  2637.     if (embed instanceof CI.nsIDOMElement) try {
  2638.       
  2639.       var params = embed.getElementsByTagName("param");
  2640.       if (!params.length) return uri.spec;
  2641.       
  2642.       var name, value, pp = [];
  2643.       for (var j = params.length; j-- > 0;) { // iteration inverse order is important for "source"!
  2644.         name = params[j].name;
  2645.         value = params[j].value;
  2646.         if(!(name && value)) continue;
  2647.         
  2648.         if (!url && name.toLowerCase() == "source") {
  2649.           try {
  2650.              url = uri.resolve(value);
  2651.              continue;
  2652.           } catch(e) {
  2653.             if (this.consoleDump)  
  2654.               this.dump("Couldn't resolve Silverlight URL " + uri.spec + " + " + value + ":" + e);
  2655.             url = uri.spec;
  2656.           }
  2657.         }
  2658.         pp.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
  2659.       }
  2660.       return (url || uri.spec) + "#!objparams#" + pp.join("&");
  2661.     } catch(e1) {
  2662.       if (this.consoleDump)  this.dump("Couldn't resolve Silverlight URL " + uri.spec + ":" + e1);
  2663.     }
  2664.     return uri.spec;
  2665.   },
  2666.   
  2667.   tagForReplacement: function(embed, pluginExtras) {
  2668.     try {
  2669.       if(!embed.ownerDocument) return;
  2670.       var win = embed.ownerDocument.defaultView.top;
  2671.       this.getExpando(win, "pe",  []).push({embed: embed, pluginExtras: pluginExtras});
  2672.       try {
  2673.         this.syncUI(embed);
  2674.       } catch(noUIex) {
  2675.         if(this.consoleDump) this.dump(noUIex);
  2676.       }
  2677.     } catch(ex) {
  2678.       if(this.consoleDump) this.dump(
  2679.         "Error tagging object [" + pluginExtras.mime + " from " + pluginExtras.url +
  2680.         " - top window " + win + ", embed " + embed +
  2681.         "] for replacement: " + ex);
  2682.     }
  2683.   },
  2684.   
  2685.   blockLegacyFrame: function(frame, uri, sync) {
  2686.     var verbose = this.consoleDump & LOG_CONTENT_BLOCK;
  2687.     if(verbose) {
  2688.       this.dump("Redirecting blocked legacy frame " + uri.spec);
  2689.     }
  2690.     var url = this.createPluginDocumentURL(uri);
  2691.     if(sync) {
  2692.       if(verbose) dump("Legacy frame plugin SYNC, setting to " + url + "\n");
  2693.       frame.src = url;
  2694.     } else {
  2695.       frame.ownerDocument.defaultView.addEventListener("load", function(ev) {
  2696.           if(verbose) dump("Legacy frame plugin ON PARENT LOAD, setting to " + url + "\n");
  2697.           ev.currentTarget.removeEventListener("load", arguments.callee, false);
  2698.           frame.src = url;
  2699.       }, false);
  2700.     }
  2701.   },
  2702.   
  2703.   createPluginDocumentURL: function(uri) {
  2704.     return 'data:text/html;charset=utf-8,' +
  2705.         encodeURIComponent('<html><head></head><body style="padding: 0px; margin: 0px"><embed src="' +
  2706.                   uri.spec + '" width="100%" height="100%"></embed></body></html>');
  2707.   },
  2708.   
  2709.   forbiddenIFrameContext: function(originURL, locationURL) {
  2710.     switch (this.forbidIFramesContext) {
  2711.       case 0: // all IFRAMES
  2712.         return true;
  2713.       case 3: // different 2nd level domain
  2714.         return this.getBaseDomain(this.getDomain(originURL)) != 
  2715.           this.getBaseDomain(this.getDomain(locationURL));
  2716.       case 2: // different domain
  2717.         return this.getDomain(originURL) != this.getDomain(locationURL);
  2718.       case 1: // different site
  2719.         return this.getSite(originURL) != this.getSite(locationURL);
  2720.      }
  2721.      return false;
  2722.   },
  2723.   
  2724.   forbiddenXBLContext: function(originURL, locationURL) {
  2725.     if (locationURL == this.nopXBL) return false; // always allow our nop binding
  2726.     
  2727.     var locationSite = this.getSite(locationURL);
  2728.     var originSite = this.getSite(originURL);
  2729.    
  2730.     switch (this.forbidXBL) {
  2731.       case 4: // allow only XBL from the same trusted site or chrome (default)
  2732.         if (locationSite != originSite) return true; // chrome is checked by the caller checkXML
  2733.       case 3: // allow only trusted XBL on trusted sites
  2734.         if (!locationSite) return true;
  2735.       case 2: // allow trusted and data: (Fx 3) XBL on trusted sites
  2736.         if (!(this.isJSEnabled(originSite) ||
  2737.               /^file:/.test(locationURL) // we trust local files to allow Linux theming
  2738.              )) return true;
  2739.       case 1: // allow trusted and data: (Fx 3) XBL on any site
  2740.         if (!(this.isJSEnabled(locationSite) || /^(?:data|file|resource):/.test(locationURL))) return true;
  2741.       case 0: // allow all XBL
  2742.         return false;
  2743.     }
  2744.     return true;
  2745.   },
  2746.   
  2747.   forbiddenXHRContext: function(originURL, locationURL) {
  2748.     var locationSite = this.getSite(locationURL);
  2749.     // var originSite = this.getSite(originURL);
  2750.     switch (this.forbidXHR) {
  2751.       case 3: // forbid all XHR
  2752.         return true;
  2753.       case 2: // allow same-site XHR only
  2754.         if (locationSite != originSite) return true;
  2755.       case 1: // allow trusted XHR targets only
  2756.         if (!(this.isJSEnabled(locationSite))) return true;
  2757.       case 0: // allow all XBL
  2758.         return false;
  2759.     }
  2760.     return true;
  2761.   },
  2762.   
  2763.   
  2764.   safeJSRx: false,
  2765.   initSafeJSRx: function() {
  2766.     try {
  2767.       this.safeJSRx = new RegExp("^\\s*" + this.getPref("safeJSRx", "") + "\\s*;?\\s*$");
  2768.     } catch(e) {
  2769.       this.safeJSRx = false;
  2770.     }
  2771.   },
  2772.   isSafeJSURL: function(url) {
  2773.     var js = url.replace(/^javascript:/i, "");
  2774.     return this.safeJSRx && js != url && this.safeJSRx.test(js);
  2775.   },
  2776.   
  2777.   isFirebugJSURL: function(url) {
  2778.     return url == "javascript: eval(__firebugTemp__);"
  2779.   },
  2780.   
  2781.   isExternalScheme: function(scheme) {
  2782.     try {
  2783.       return this.siteUtils.ios.getProtocolHandler(scheme).scheme != scheme;
  2784.     } catch(e) {
  2785.       return false;
  2786.     }
  2787.   },
  2788.   normalizeExternalURI: function(uri) {
  2789.     var uriSpec = uri.spec;
  2790.     var uriValid = this.uriValidator.validate(uriSpec);
  2791.     var fixURI = this.getPref("fixURI", true) && 
  2792.       this.getPref("fixURI.exclude", "").split(/[^\w\-]+/)
  2793.           .indexOf(uri.scheme) < 0;
  2794.     var msg;
  2795.     if (!uriValid) {
  2796.       if (fixURI) {
  2797.         uriSpec = uriSpec
  2798.             .replace(/[\s\x01-\x1f\0]/g, " ") // whitespace + null + control chars all to space
  2799.             .replace(/%[01][\da-f]/gi, "%20"); // ditto for already encoded items
  2800.         if (uriSpec != uri.spec) {
  2801.           if (this.consoleDump) this.dump("Fixing URI: " + uri.spec + " into " + uriSpec);
  2802.           if (uriValid !== false || (uriValid = this.uriValidator.validate(uriSpec))) {
  2803.             uri.spec = uriSpec;
  2804.           }
  2805.         }
  2806.       }
  2807.       if (uriValid === false) {
  2808.         msg = "Rejected invalid URI: " + uriSpec;
  2809.         if (this.consoleDump) this.dump(msg);
  2810.         this.log("[NoScript URI Validator] " + msg);
  2811.         return false;
  2812.       }
  2813.     }
  2814.     // encode all you can (i.e. don't touch valid encoded and delims)
  2815.     if (fixURI) {
  2816.       try {
  2817.         uriSpec = uriSpec.replace(/[^%]|%(?![\da-f]{2})/gi, encodeURI); 
  2818.         if (uriSpec != uri.spec) {
  2819.           if (this.consoleDump) this.dump("Encoded URI: " + uri.spec + " to " + uriSpec);
  2820.           uri.spec = uriSpec;
  2821.         }
  2822.       } catch(ex) {
  2823.         msg = "Error assigning encoded URI: " + uriSpec + ", " + ex;
  2824.         if (this.consoleDump) this.dump(msg);
  2825.         this.log("[NoScript URI Validator] " + msg);
  2826.         return false;
  2827.       }
  2828.     }
  2829.     return true;
  2830.   },
  2831.   
  2832.   syncUI: function(domNode) {
  2833.     const browser = this.domUtils.findBrowserForNode(domNode);
  2834.     if (browser && (browser.docShell instanceof CI.nsIWebProgress) && !browser.docShell.isLoadingDocument) {
  2835.       var overlay = this.findOverlay(browser);
  2836.       if(overlay) overlay.syncUI(domNode.ownerDocument.defaultView.top);
  2837.     }
  2838.   },
  2839.   
  2840.   objectWhitelist: {},
  2841.   ALL_TYPES: ["*"],
  2842.   objectWhitelistLen: 0,
  2843.   isAllowedObject: function(url, mime, site) {
  2844.     var types = this.objectWhitelist[url] || null;
  2845.     if (types && (types == this.ALL_TYPES || types.indexOf(mime) > -1)) 
  2846.       return true;
  2847.     
  2848.     if (arguments.length < 3) site = this.getSite(url);
  2849.     
  2850.     var types = site && this.objectWhitelist[site] || null;
  2851.     return types && (types == this.ALL_TYPES || types.indexOf(mime) > -1);
  2852.   },
  2853.   
  2854.   allowObject: function(url, mime) {
  2855.     if (url in this.objectWhitelist) {
  2856.       var types = this.objectWhitelist[url];
  2857.       if(mime == "*") {
  2858.         if(types == this.ALL_TYPES) return;
  2859.         types = this.ALL_TYPES;
  2860.       } else {
  2861.         if(types.indexOf(mime) > -1) return;
  2862.         types.push(mime);
  2863.       }
  2864.     } else {
  2865.       this.objectWhitelist[url] = mime == "*" ? this.ALL_TYPES : [mime];
  2866.     }
  2867.     this.objectWhitelistLen++;
  2868.   },
  2869.   
  2870.   resetAllowedObjects: function() {
  2871.     this.objectWhitelist = {};
  2872.     this.objectWhitelistLen = 0;
  2873.   },
  2874.   
  2875.   
  2876.   countObject: function(embed, site) {
  2877.     if(!site) return;
  2878.     
  2879.     var win = embed.ownerDocument.defaultView.top;
  2880.     var os = this.getExpando(win, "objectSites");
  2881.     if(os) {
  2882.       if(os.indexOf(site) < 0) os.push(site);
  2883.     } else {
  2884.       this.setExpando(win, "objectSites", [site]);
  2885.     }
  2886.   },
  2887.   
  2888.   getPluginExtras: function(obj) {
  2889.     return this.getExpando(obj, "pluginExtras");
  2890.   },
  2891.   setPluginExtras: function(obj, extras) {
  2892.     this.setExpando(obj, "pluginExtras", extras);
  2893.     if (this.consoleDump & LOG_CONTENT_BLOCK) this.dump("Setting plugin extras on " + obj + " -> " + (this.getPluginExtras(obj) == extras)
  2894.       + ", " + (extras && extras.toSource())  );
  2895.     return extras;
  2896.   },
  2897.   
  2898.   getExpando: function(domObject, key, defValue) {
  2899.     return domObject && domObject.__noscriptStorage && domObject.__noscriptStorage[key] || 
  2900.            (defValue ? this.setExpando(domObject, key, defValue) : null);
  2901.   },
  2902.   setExpando: function(domObject, key, value) {
  2903.     if (!domObject) return null;
  2904.     if (!domObject.__noscriptStorage) domObject.__noscriptStorage = {};
  2905.     if (domObject.__noscriptStorage) domObject.__noscriptStorage[key] = value;
  2906.     else if(this.consoleDump) this.dump("Warning: cannot set expando " + key + " to value " + value);
  2907.     return value;
  2908.   },
  2909.   
  2910.   cleanupBrowser: function(browser) {
  2911.     delete browser.__noscriptStorage;
  2912.   },
  2913.   
  2914.   hasVisibleLinks: function(document) {
  2915.     var links = document.links;
  2916.     var position;
  2917.     for (var j = 0, l; (l = links[j]); j++) {
  2918.       if (l && l.href && /^https?/i.test(l.href) && l.firstChild) {
  2919.         if(l.offsetWidth && l.offsetHeight) return true;
  2920.         position = l.style.position;
  2921.         try {
  2922.           l.style.position = "absolute";
  2923.           if(l.offsetWidth && l.offsetHeight) return true;
  2924.         } finally {
  2925.           l.style.position = position;
  2926.         }
  2927.       }
  2928.     }
  2929.     return false;
  2930.   },
  2931.   detectJSRedirects: function(document) {
  2932.     if (this.jsredirectIgnore || this.jsEnabled) return 0;
  2933.     try {
  2934.       if (!/^https?:/.test(document.documentURI)) return 0;
  2935.       var hasVisibleLinks = this.hasVisibleLinks(document);
  2936.       if (!this.jsredirectForceShow && hasVisibleLinks ||
  2937.           this.isJSEnabled(this.getSite(document.documentURI))) 
  2938.         return 0;
  2939.       var j, len;
  2940.       var seen = [];
  2941.       var body = document.body;
  2942.       var cstyle = document.defaultView.getComputedStyle(body, "");
  2943.       if (cstyle) {
  2944.         if (cstyle.visibility != "visible") {
  2945.           body.style.visibility = "visible";
  2946.         }
  2947.         if (cstyle.display == "none") {
  2948.           body.style.display = "block";
  2949.         }
  2950.       }
  2951.       if (!hasVisibleLinks && document.links[0]) {
  2952.         var links = document.links;
  2953.         var l;
  2954.         for (j = 0, len = links.length; j < len; j++) {
  2955.           l = links[j];
  2956.           if (!(l.href && /^https?/.test(l.href))) continue;
  2957.           l = body.appendChild(l.cloneNode(true));
  2958.           l.style.visibility = "visible";
  2959.           l.style.display = "block";
  2960.           seen.push(l.href);
  2961.         }
  2962.       }
  2963.       const scripts = document.getElementsByTagName("script");
  2964.       if (!scripts[0]) return 0;
  2965.       var follow = false;
  2966.       const findURL = /(?:(?:\b(?:open|replace)\s*\(|(?:\b(?:href|location|src|path|pathname|search)|(?:[Pp]ath|UR[IL]|[uU]r[il]))\s*=)\s*['"]|['"](?=https?:\/\/\w|\w*[\.\/\?]))([\?\/\.\w\-%\&][^\s'"]*)/g;
  2967.       findURL.lastIndex = 0;
  2968.       var code, m, url, a;
  2969.       var container = null;
  2970.       var window;
  2971.    
  2972.       for (j = 0, len = scripts.length; j < len; j++) {
  2973.         code = scripts[j].innerHTML;
  2974.         while ((m = findURL.exec(code))) {
  2975.           if (!container) {
  2976.              container = document.createElement("div");
  2977.              with(container.style) {
  2978.                backgroundImage = 'url("' + this.pluginPlaceholder + '")';
  2979.                backgroundRepeat = "no-repeat";
  2980.                backgroundPosition = "2px 2px";
  2981.                padding = "4px 4px 4px 40px";
  2982.                display = "block";
  2983.                minHeight = "32px";
  2984.                textAlign = "left";
  2985.              }
  2986.              window = document.defaultView;
  2987.              follow = this.jsredirectFollow && window == window.top &&  
  2988.                !window.frames[0] &&
  2989.                !document.evaluate('//body[normalize-space()!=""]', document, null, 
  2990.                  CI.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  2991.              document.body.appendChild(container);
  2992.           }
  2993.           url = m[1];
  2994.           a = document.createElement("a");
  2995.           a.href = url;
  2996.           container.appendChild(a);
  2997.           if (a.href.toLowerCase().indexOf("http") != 0 || seen.indexOf(a.href) > -1) {
  2998.              container.removeChild(a);
  2999.              continue;
  3000.           }
  3001.           seen.push(a.href);
  3002.           a.appendChild(document.createTextNode(a.href));
  3003.           container.appendChild(document.createElement("br"));
  3004.         }
  3005.         
  3006.         if (follow && seen.length == 1) {
  3007.           this.log("[NoScript Following JS Redirection]: " + seen[0] + " FROM " + document.location.href); 
  3008.           
  3009.           this.doFollowMetaRefresh(mi = {
  3010.             baseURI: this.siteUtils.ios.newURI(document.documentURI, null, null),
  3011.             uri: seen[0],
  3012.             document: document,
  3013.             docShell: this.domUtils.getDocShellFromWindow(document.defaultView)
  3014.           });
  3015.           
  3016.         }
  3017.       }
  3018.       return seen.length;
  3019.     } catch(e) { 
  3020.       this.dump(e.message + " while processing JS redirects");
  3021.       return 0; 
  3022.     }
  3023.   }
  3024. ,
  3025.   processScriptElements: function(document, sites) {
  3026.     var scripts = document.getElementsByTagName("script");
  3027.     var scount = scripts.length;
  3028.     if (scount) {
  3029.       const HTMLElement = CI.nsIDOMHTMLElement;
  3030.       sites.scriptCount += scount;
  3031.       var script, scriptSrc;
  3032.       var nselForce = this.nselForce && sites.length && this.isJSEnabled(sites[sites.length - 1]);
  3033.       var isHTMLScript;
  3034.       while (scount-- > 0) {
  3035.         script = scripts.item(scount);
  3036.         isHTMLScript = script instanceof HTMLElement;
  3037.         if (isHTMLScript) {
  3038.           scriptSrc = script.src;
  3039.         } else if(script) {
  3040.           scriptSrc = script.getAttribute("src");
  3041.           if (!/^[a-z]+:\/\//i.test(scriptSrc)) continue;
  3042.         } else continue;
  3043.         
  3044.         scriptSrc = this.getSite(scriptSrc);
  3045.         if (scriptSrc) {
  3046.           sites.push(scriptSrc);
  3047.           if (nselForce && isHTMLScript && !this.isJSEnabled(scriptSrc)) {
  3048.             this.showNextNoscriptElement(script);
  3049.           }
  3050.         }
  3051.       }
  3052.       
  3053.      
  3054.     }
  3055.   },
  3056.   
  3057.   showNextNoscriptElement: function(script, doc) { 
  3058.     const HTMLElement = CI.nsIDOMHTMLElement;
  3059.     var child, el, ss, j;
  3060.     for (var node = script; node = node.nextSibling;) {
  3061.       try {
  3062.         if (node instanceof HTMLElement) {
  3063.           if (node.tagName.toUpperCase() != "NOSCRIPT") return;
  3064.           if (node.getAttribute("class") == "noscript-show") return;
  3065.           node.setAttribute("class", "noscript-show");
  3066.           child = node.firstChild;
  3067.           if (child.nodeType != 3) return;
  3068.           el = node.ownerDocument.createElement("span");
  3069.           el.className = "noscript-show";
  3070.           el.innerHTML = child.nodeValue;
  3071.           // remove noscript script children, see evite.com bug 
  3072.           ss = el.getElementsByTagName("script");
  3073.           for(j = ss.length; j-- > 0;) el.removeChild(ss[j]);
  3074.           node.replaceChild(el, child);
  3075.         }
  3076.       } catch(e) {
  3077.         this.dump(e.message + " while showing NOSCRIPT element");
  3078.       }
  3079.     }
  3080.   },
  3081.   
  3082.   metaRefreshWhitelist: {},
  3083.   processMetaRefresh: function(document, notifyCallback) {
  3084.     var docShell = this.domUtils.getDocShellFromWindow(document.defaultView);
  3085.     if (!this.forbidMetaRefresh ||    
  3086.        this.metaRefreshWhitelist[document.documentURI] ||
  3087.        this.isJSEnabled(this.getSite(document.documentURI)) ||
  3088.        !document.getElementsByTagName("noscript")[0]
  3089.        ) {
  3090.       if (!docShell.allowMetaRedirects) this.disableMetaRefresh(docShell); // refresh blocker courtesy
  3091.       return;
  3092.     }
  3093.     try {
  3094.       var refresh, content, timeout, uri;
  3095.       var rr = document.getElementsByTagName("meta");
  3096.       for (var j = 0; (refresh = rr[j]); j++) {
  3097.         if (!/refresh/i.test(refresh.httpEquiv)) continue;
  3098.         content = refresh.content.split(/[,;]/, 2);
  3099.         uri = content[1];
  3100.         if (uri) {
  3101.           if (notifyCallback && !(document.documentURI in this.metaRefreshWhitelist)) {
  3102.             timeout = content[0];
  3103.             uri = uri.replace (/^\s*/, "").replace (/^URL/i, "URL").split("URL=", 2)[1];
  3104.             try {
  3105.               notifyCallback({ 
  3106.                 docShell: docShell,
  3107.                 document: document,
  3108.                 baseURI: docShell.currentURI,
  3109.                 uri: uri, 
  3110.                 timeout: timeout
  3111.               });
  3112.             } catch(e) {
  3113.               dump("[NoScript]: " + e + " notifying meta refresh at " + document.documentURI + "\n");
  3114.             }
  3115.           }
  3116.           document.defaultView.addEventListener("pagehide", function(ev) {
  3117.               ev.currentTarget.removeEventListener("pagehide", arguments.callee, false);
  3118.               docShell.allowMetaRedirects = true;
  3119.               document = docShell = null;
  3120.           }, false);
  3121.           this.disableMetaRefresh(docShell);
  3122.           return;
  3123.         }
  3124.       }
  3125.     } catch(e) {
  3126.       dump("[NoScript]: " + e + " processing meta refresh at " + document.documentURI + "\n");
  3127.     }
  3128.   },
  3129.   doFollowMetaRefresh: function(metaRefreshInfo, forceRemember) {
  3130.     if (forceRemember || this.getPref("forbidMetaRefresh.remember", false)) {
  3131.       var document = metaRefreshInfo.document;
  3132.       this.metaRefreshWhitelist[document.documentURI] = metaRefreshInfo.uri;
  3133.     }
  3134.     var docShell = metaRefreshInfo.docShell;
  3135.     this.enableMetaRefresh(metaRefreshInfo.docShell);
  3136.     if (docShell instanceof CI.nsIRefreshURI) {
  3137.       docShell.setupRefreshURIFromHeader(metaRefreshInfo.baseURI, "0;" + metaRefreshInfo.uri);
  3138.     }
  3139.   },
  3140.   doBlockMetaRefresh: function(metaRefreshInfo) {
  3141.     if (this.getPref("forbidMetaRefresh.remember", true)) {
  3142.       var document = metaRefreshInfo.document;
  3143.       this.metaRefreshWhitelist[document.documentURI] = null;
  3144.     }
  3145.   },
  3146.   
  3147.   enableMetaRefresh: function(docShell) {
  3148.     if (docShell) {
  3149.       docShell.allowMetaRedirects = true;
  3150.       docShell.resumeRefreshURIs();
  3151.       // if(this.consoleDump) dump("Enabled META refresh on " + (docShell.currentURI && docShell.currentURI.spec) + "\n");
  3152.     }
  3153.   },
  3154.   disableMetaRefresh: function(docShell) {
  3155.     if (docShell) {
  3156.       docShell.suspendRefreshURIs();
  3157.       docShell.allowMetaRedirects = false;
  3158.       if (docShell instanceof CI.nsIRefreshURI) {
  3159.         docShell.cancelRefreshURITimers();
  3160.       }
  3161.       // if(this.consoleDump) dump("Disabled META refresh on " + (docShell.currentURI && docShell.currentURI.spec) + "\n");
  3162.     }
  3163.   },
  3164.   
  3165.   handleBookmark: function(url, openCallback) {
  3166.     if (!url) return true;
  3167.     const allowBookmarklets = !this.getPref("forbidBookmarklets", false);
  3168.     const allowBookmarks = this.getPref("allowBookmarks", false);
  3169.     if ((!this.jsEnabled) && 
  3170.       (allowBookmarks || allowBookmarklets)) {
  3171.       try {
  3172.         if (allowBookmarklets && /^\s*(?:javascript|data):/i.test(url)) {
  3173.           var ret = this.executeJSURL(url, openCallback);
  3174.         } else if(allowBookmarks) {
  3175.           this.setJSEnabled(this.getSite(url), true);
  3176.         }
  3177.         
  3178.         return ret;
  3179.       } catch(silentEx) {
  3180.         dump(silentEx);
  3181.       }
  3182.     }
  3183.     return false;
  3184.   },
  3185.   
  3186.   executeJSURL: function(url, openCallback) {
  3187.     var browserWindow = DOMUtils.mostRecentBrowserWindow;
  3188.     var browser = browserWindow.getBrowser().selectedBrowser;
  3189.     if(!browser) return false;
  3190.     
  3191.     var window = browser.contentWindow;
  3192.     if(!window) return false;
  3193.     
  3194.     var site = this.getSite(window.document.documentURI) || this.getExpando(browser, "jsSite");
  3195.     if (!this.isJSEnabled(site)) {
  3196.       if(this.consoleDump) this.dump("Executing JS URL " + url + " on site " + site);
  3197.       var snapshots = {
  3198.         trusted: this.jsPolicySites.sitesString,
  3199.         untrusted: this.untrustedSites.sitesString,
  3200.         docJS: browser.webNavigation.allowJavascript
  3201.       };
  3202.       var async = /^\s*data:/i.test(url) || Components.utils && typeof(/ /) == "object"; // async evaluation, after bug 351633 landing
  3203.       try {
  3204.         browser.webNavigation.allowJavascript = true;
  3205.         this.setTemp(site, true);
  3206.         this.setJSEnabled(site, true)
  3207.         if (async) {
  3208.           var sandbox = Components.utils.Sandbox(window);
  3209.           sandbox.window = window;
  3210.           sandbox.jsURL = url;
  3211.           Components.utils.evalInSandbox("window.location.href = jsURL", sandbox);
  3212.         } else {
  3213.           openCallback(url);
  3214.         }
  3215.         return true;
  3216.       } finally {
  3217.         if(async) {
  3218.           this.delayExec(this.postExecuteJSURL, 0, browser, site, snapshots);
  3219.         } else {
  3220.           this.postExecuteJSURL(browser, site, snapshots);
  3221.         }
  3222.       }
  3223.     }
  3224.     
  3225.     return false;
  3226.   },
  3227.   
  3228.   postExecuteJSURL: function(browser, site, snapshots, dsJS) {
  3229.     if (this.consoleDump & LOG_JS)
  3230.       this.dump("Restoring snapshot permissions on " + site + "/" + (browser.webNavigation.isLoadingDocument ? "loading" : browser.webNavigation.currentURI.spec));
  3231.     this.persistUntrusted(snapshots.untrusted); 
  3232.     this.flushCAPS(snapshots.trusted);
  3233.     this.setExpando(browser, "jsSite", site);
  3234.     if (!browser.webNavigation.isLoadingDocument && this.getSite(browser.webNavigation.currentURI.spec) == site)
  3235.       browser.webNavigation.allowJavascript = snapshots.docJS;
  3236.   },
  3237.  
  3238.   mimeEssentials: function(mime) {
  3239.      return mime && mime.replace(/^application\/(?:x-)?/, "") || "";
  3240.   },
  3241.   urlEssentials: function(s) {
  3242.     // remove query, hash and intermediate path
  3243.     return s.replace(/[#\?].*/g, '').replace(/(.*?\w\/).+?(\/[^\/]+)$/, '$1...$2');
  3244.   },
  3245.   cssMimeIcon: function(mime, size) {
  3246.     return "url(\"moz-icon://noscript?size=" + size + "&contentType=" + mime.replace(/[^\w-\/]/g, "") + "\")";
  3247.   },
  3248.   
  3249.   
  3250.   findObjectAncestor: function(embed) {
  3251.     if (embed instanceof CI.nsIDOMHTMLEmbedElement) {
  3252.       const objType = CI.nsIDOMHTMLObjectElement;
  3253.       for (var o = embed; (o = o.parentNode);) {
  3254.         if (o instanceof objType) return o;
  3255.       }
  3256.     }
  3257.     return embed;
  3258.   },
  3259.   
  3260.   findPluginExtras: function(document) {
  3261.     return this.getExpando(document.defaultView, "pluginExtras", []);
  3262.   },
  3263.   
  3264.   _objectTypes: null, 
  3265.   processObjectElements: function(document, sites) {
  3266.     var pluginExtras = this.findPluginExtras(document);
  3267.     sites.pluginCount += pluginExtras.length;
  3268.     sites.pluginExtras.push(pluginExtras);
  3269.  
  3270.     var collapse = this.collapseObject;
  3271.     
  3272.     const types = this._objectTypes || 
  3273.           (this._objectTypes = {
  3274.             embed:  CI.nsIDOMHTMLEmbedElement, 
  3275.             applet: CI.nsIDOMHTMLAppletElement,
  3276.             iframe: CI.nsIDOMHTMLIFrameElement,
  3277.             object: CI.nsIDOMHTMLObjectElement
  3278.           });
  3279.  
  3280.     const htmlNS = "http://www.w3.org/1999/xhtml";
  3281.     
  3282.     var objectType;
  3283.     var count, objects, object;
  3284.     var anchor, innerDiv, iconSize;
  3285.     var extras;
  3286.     var style, cssLen, cssCount, cssProp, cssDef;
  3287.     var forcedCSS, style, astyle;
  3288.     
  3289.     var replacements = null;
  3290.     
  3291.     for (var objectTag in types) {
  3292.       objects = document.getElementsByTagName(objectTag);
  3293.       objectType = types[objectTag];
  3294.       for (count = objects.length; count-- > 0;) {
  3295.         try { 
  3296.           object = objects.item(count); 
  3297.         } catch(e) { 
  3298.           if (this.consoleDump) this.dump(e);
  3299.           continue; 
  3300.         }
  3301.         if (!(object instanceof objectType) || // wrong type instantiated for this tag?!
  3302.             this.findObjectAncestor(object) != object // skip "embed" if nested into "object"
  3303.          ) continue;
  3304.          
  3305.         extras = this.getPluginExtras(object);
  3306.         
  3307.         
  3308.         if (extras) {
  3309.           
  3310.           sites.pluginCount++;
  3311.           
  3312.           if (!forcedCSS) {
  3313.             
  3314.             forcedCSS = ";";
  3315.            
  3316.             try {
  3317.               if (object.parentNode == document.body && !object.nextSibling) { 
  3318.                 // raw plugin content
  3319.                 collapse = false;
  3320.                 forcedCSS = ";-moz-outline-style: none !important;";
  3321.               }
  3322.             } catch(e) {}
  3323.             
  3324.           }
  3325.  
  3326.           try {
  3327.  
  3328.             extras.site = this.getSite(extras.url);
  3329.             
  3330.             if(!this.showUntrustedPlaceholder && this.isUntrusted(extras.site)) 
  3331.               continue;
  3332.             
  3333.             extras.tag = "<" + objectTag.toUpperCase() + ">";
  3334.             extras.title =  extras.tag + ", " +  
  3335.                 this.mimeEssentials(extras.mime) + "@" + extras.url;
  3336.             
  3337.            if ((extras.alt = object.getAttribute("alt")))
  3338.               extras.title += ' "' + extras.alt + '"'
  3339.             
  3340.             
  3341.             anchor = document.createElementNS(htmlNS, "a");
  3342.             anchor.id = object.id;
  3343.             anchor.href = extras.url;
  3344.             anchor.setAttribute("title", extras.title);
  3345.             
  3346.             this.setPluginExtras(anchor, extras);
  3347.             this.setExpando(anchor, "removedPlugin", object);
  3348.             
  3349.             (replacements = replacements || []).push({object: object, placeholder: anchor, extras: extras});
  3350.  
  3351.             if (this.showPlaceholder) {
  3352.               anchor.addEventListener("click", this.objectClickListener.bind(this), true);
  3353.               anchor.className = "__noscriptPlaceholder__";
  3354.             } else {
  3355.                anchor.className = "";
  3356.                if(collapse) anchor.style.display = "none";
  3357.                else anchor.style.visibility = "hidden";
  3358.                continue;
  3359.             }
  3360.             
  3361.             innerDiv = document.createElementNS(htmlNS, "div");
  3362.             innerDiv.className = "__noscriptPlaceholder__1";
  3363.             
  3364.             with(anchor.style) {
  3365.               padding = margin = borderWidth = "0px";
  3366.               MozOutlineOffset = "-1px"; 
  3367.               display = "inline";
  3368.             }
  3369.             
  3370.             if (!collapse) {
  3371.               cssDef = "";
  3372.               style = document.defaultView.getComputedStyle(object, null);
  3373.               if (style) {
  3374.                 for (cssCount = 0, cssLen = style.length; cssCount < cssLen; cssCount++) {
  3375.                   cssProp = style.item(cssCount);
  3376.                   cssDef += cssProp + ": " + style.getPropertyValue(cssProp) + ";";
  3377.                 }
  3378.                 
  3379.                 innerDiv.setAttribute("style", cssDef + forcedCSS);
  3380.                 
  3381.                 if (style.width == "100%" || style.height == "100%") {
  3382.                   anchor.style.width = style.width;
  3383.                   anchor.style.height = style.height;
  3384.                   anchor.style.display = "block";
  3385.                 }
  3386.               }
  3387.               innerDiv.style.minWidth = "32px";
  3388.               innerDiv.style.minHeight = "32px";
  3389.             } else {
  3390.               innerDiv.setAttribute("style", forcedCSS);
  3391.             }
  3392.             
  3393.             if(collapse || innerDiv.style.display == "none" || innerDiv.style.visibility == "hidden") {
  3394.               innerDiv.style.width = anchor.style.width = "32px";
  3395.               innerDiv.style.height = anchor.style.height = "32px";
  3396.             }
  3397.               
  3398.             innerDiv.style.display = "block";
  3399.             innerDiv.style.visibility = "visible";
  3400.             
  3401.  
  3402.             anchor.appendChild(innerDiv);
  3403.             
  3404.             // icon div
  3405.             innerDiv = innerDiv.appendChild(document.createElementNS(htmlNS, "div"));
  3406.             innerDiv.className = "__noscriptPlaceholder__2";
  3407.             
  3408.             if(collapse || style && parseInt(style.width) < 64 && parseInt(style.height) < 64) {
  3409.               innerDiv.style.backgroundPosition = "bottom right";
  3410.               iconSize = 16;
  3411.             } else {
  3412.               iconSize = 32;
  3413.               innerDiv.style.backgroundPosition = "center";
  3414.             }
  3415.             innerDiv.style.backgroundImage = this.cssMimeIcon(extras.mime, iconSize);
  3416.             
  3417.           } catch(objectEx) {
  3418.             dump("NoScript: " + objectEx + " processing plugin " + count + "@" + document.documentURI + "\n");
  3419.           }
  3420.         }
  3421.       }
  3422.     }
  3423.  
  3424.     if (replacements) {
  3425.       this.delayExec(this.createPlaceholders, 0, replacements, pluginExtras);
  3426.     }
  3427.   },
  3428.   
  3429.   createPlaceholders: function(replacements, pluginExtras) {
  3430.     for each(var r in replacements) {
  3431.       if (r.object.parentNode) {
  3432.         r.object.parentNode.replaceChild(r.placeholder, r.object);
  3433.         r.extras.placeholder = r.placeholder;
  3434.         this._collectPluginExtras(pluginExtras, r.extras);
  3435.       }
  3436.     }
  3437.   },
  3438.   
  3439.   objectClickListener: {
  3440.     bind: function(ns) {
  3441.       this._clickListener.ns = ns;
  3442.       return this._clickListener;
  3443.     },
  3444.     _clickListener: function(ev) {
  3445.       if (ev.button) return;
  3446.       
  3447.      
  3448.       const anchor = ev.currentTarget;
  3449.       const ns = arguments.callee.ns;
  3450.       const object = ns.getExpando(anchor, "removedPlugin");
  3451.       
  3452.       if (object) try {
  3453.         if (ev.shiftKey) {
  3454.           anchor.style.display = "none";
  3455.           return;
  3456.         }
  3457.         ns.checkAndEnablePlaceholder(anchor, object);
  3458.       } finally {
  3459.         ev.preventDefault();
  3460.         ev.cancelBubble = true;
  3461.       }
  3462.     }
  3463.   },
  3464.   
  3465.   checkAndEnablePlaceholder: function(anchor, object) {
  3466.     if (!(object || (object = this.getExpando(anchor, "removedPlugin")))) 
  3467.       return;
  3468.     
  3469.     const extras = this.getPluginExtras(anchor);
  3470.     const browser = this.domUtils.findBrowserForNode(anchor);
  3471.  
  3472.     if (!(extras && extras.url && extras.mime // && cache
  3473.       )) return;
  3474.    
  3475.     this.delayExec(this.checkAndEnableObject, 1,
  3476.       {
  3477.         browser: browser,
  3478.         window: browser.ownerDocument.defaultView,
  3479.         extras: extras,
  3480.         anchor: anchor,
  3481.         object: object
  3482.       });
  3483.   },
  3484.   
  3485.   confirmEnableObject: function(win, extras) {
  3486.     return win.noscriptUtil.confirm(
  3487.       this.getAllowObjectMessage(extras.url, 
  3488.           (extras.tag || "<OBJECT>") + ", " + extras.mime), 
  3489.       "confirmUnblock"
  3490.     );
  3491.   },
  3492.   
  3493.   checkAndEnableObject: function(ctx) {
  3494.     var extras = ctx.extras;
  3495.     if (this.confirmEnableObject(ctx.window, extras)) {
  3496.  
  3497.       var mime = extras.mime;
  3498.       var url = extras.url;
  3499.       
  3500.       this.allowObject(url, mime);
  3501.       var doc = ctx.anchor.ownerDocument;
  3502.       if (mime == doc.contentType && 
  3503.           ctx.anchor == doc.body.firstChild && 
  3504.           ctx.anchor == doc.body.lastChild) { // stand-alone plugin
  3505.           doc.location.reload();
  3506.       } else if (this.requireReloadRegExp && this.requireReloadRegExp.test(mime)) {
  3507.         this.quickReload(this.domUtils.getDocShellFromWindow(doc.defaultView));
  3508.       } else if (this.getExpando(ctx, "silverlight")) {
  3509.         this.allowObject(doc.documentURI, mime);
  3510.         this.quickReload(this.domUtils.getDocShellFromWindow(doc.defaultView));
  3511.       } else {
  3512.         this.setExpando(ctx.anchor, "removedPlugin", null);
  3513.         extras.placeholder = null;
  3514.         this.delayExec(function() {
  3515.           var obj = ctx.object.cloneNode(true);
  3516.           ctx.anchor.parentNode.replaceChild(obj, ctx.anchor);
  3517.           this.setExpando(obj, "allowed", true);
  3518.           var pluginExtras = this.findPluginExtras(ctx.ownerDocument);
  3519.           if(pluginExtras) {
  3520.             var pos = pluginExtras.indexOf(extras);
  3521.             if(pos > -1) pluginExtras.splice(pos, 1);
  3522.           }
  3523.           ctx = null;
  3524.         }, 10);
  3525.         return;
  3526.       }
  3527.     }
  3528.     ctx = null;
  3529.   },
  3530.  
  3531.   getSites: function(browser) {
  3532.     var sites = [];
  3533.     sites.scriptCount = 0;
  3534.     sites.pluginCount = 0;
  3535.     sites.pluginExtras = [];
  3536.     sites.pluginSites = [];
  3537.  
  3538.     try {
  3539.       sites = this._enumerateSites(browser, sites);
  3540.     } catch(ex) {
  3541.       if (this.consoleDump) this.dump("Error enumerating sites: " + ex + "," + ex.stack);
  3542.     }
  3543.     return sites;
  3544.   },
  3545.   
  3546.   _attachPluginExtras: function(win) {
  3547.     try {
  3548.        var pe = this.getExpando(win, "pe");
  3549.        if (!pe) return;
  3550.        for (var o, j = pe.length; j-- > 0;) {
  3551.          o = pe[j];
  3552.          try {
  3553.            if (this.getExpando(o, "silverlight")) {
  3554.              o.embed = this._attachSilverlightExtras(o.embed, o.pluginExtras);
  3555.              if (!o.embed) continue; // skip unconiditionally to prevent in-page Silverlight placeholders
  3556.            }
  3557.            this.setPluginExtras(this.findObjectAncestor(o.embed), o.pluginExtras);
  3558.           } catch(e1) { 
  3559.             if(this.consoleDump & LOG_CONTENT_BLOCK) 
  3560.               this.dump("Error setting plugin extras: " + 
  3561.                 (o && o.pluginExtras && o.pluginExtras.url) + ", " + e1); 
  3562.           }
  3563.        }
  3564.        this.setExpando(win, "pe", null);
  3565.     } catch(e2) {
  3566.       if(this.consoleDump & LOG_CONTENT_BLOCK) this.dump("Error attaching plugin extras: " + e2); 
  3567.     }
  3568.   },
  3569.   
  3570.   _collectPluginExtras: function(pluginExtras, extras) {
  3571.     for (var e, j = pluginExtras.length; j-- > 0;) {
  3572.       e = pluginExtras[j];
  3573.       if (e == extras) return false;
  3574.       if (e.mime == extras.mime && e.url == extras.url) {
  3575.         if (!e.placeholder) {
  3576.           pluginExtras.splice(j, 1, extras);
  3577.           return true;
  3578.         }
  3579.         return false;
  3580.       }
  3581.     }
  3582.     pluginExtras.push(extras);
  3583.     return true;
  3584.   },
  3585.  
  3586.   _silverlightInstalledHack: null,
  3587.   applySilverlightPatch: function(doc) {
  3588.     try {
  3589.       if(!this._silverlightInstalledHack) {
  3590.         this._silverlightInstalledHack = "javascript:" + escape("(" + 
  3591.         function() {
  3592.           HTMLObjectElement.prototype.IsVersionSupported = function(n) { return this.type == 'application/x-silverlight'; };
  3593.         }.toSource()
  3594.         + ")()");
  3595.       }
  3596.       var win = doc && doc.defaultView;
  3597.       if (!win || this.getExpando(win, "silverlightHack")) return;
  3598.       if (this.consoleDump & LOG_CONTENT_BLOCK) this.dump("Emulating SilverlightControl.IsVersionSupported()");
  3599.       this.setExpando(win, "silverlightHack", true);
  3600.       win.location.href = this._silverlightInstalledHack;
  3601.     } catch(e) {
  3602.        if (this.consoleDump) this.dump(e);
  3603.     }
  3604.   },
  3605.   
  3606.   _attachSilverlightExtras: function(embed, extras) {
  3607.     extras.silverlight = true;
  3608.     var pluginExtras = this.findPluginExtras(embed.ownerDocument);
  3609.     if (this._collectPluginExtras(pluginExtras, extras)) {
  3610.       extras.site = this.getSite(extras.url);
  3611.       try {
  3612.         // try to work around the IsInstalled() Silverlight machinery
  3613.         if (!embed.firstChild) { // dummy embed
  3614.           exras.dummy = true;
  3615.           return null;
  3616.         }
  3617.         extras.dummy = false;
  3618.       } catch(e) {
  3619.         if(this.consoleDump) this.dump(e);
  3620.       }
  3621.     }
  3622.     return embed;
  3623.   },
  3624.   
  3625.   _enumerateSites: function(browser, sites) {
  3626.  
  3627.     const nsIWebNavigation = CI.nsIWebNavigation;
  3628.     const nsIDocShell = CI.nsIDocShell;
  3629.     
  3630.     const docShells = browser.docShell.getDocShellEnumerator(
  3631.         CI.nsIDocShellTreeItem.typeContent,
  3632.         browser.docShell.ENUMERATE_FORWARDS
  3633.     );
  3634.     
  3635.     var docShell, docURI, url, win;
  3636.  
  3637.     var cache, redir, tmpPluginCount;
  3638.     
  3639.     var document, domain;
  3640.     while (docShells.hasMoreElements()) {
  3641.        
  3642.        docShell = docShells.getNext();
  3643.        document = (docShell instanceof nsIDocShell) &&
  3644.                   docShell.contentViewer && docShell.contentViewer.DOMDocument;
  3645.        if (!document) continue;
  3646.        
  3647.        // Truncate title as needed
  3648.        if (this.truncateTitle && document.title.length > this.truncateTitleLen) {
  3649.          document.title = document.title.substring(0, this.truncateTitleLen);
  3650.        }
  3651.        
  3652.        // Collect document / cached plugin URLs
  3653.        win = document.defaultView;
  3654.        url = this.getSite(docURI = document.documentURI);
  3655.        if (url) {
  3656.          try {
  3657.            if (document.domain && document.domain != this.getDomain(url, true) && url != "chrome:" && url != "about:blank") {
  3658.              sites.unshift(document.domain);
  3659.            }
  3660.          } catch(e) {}
  3661.          sites.push(url);
  3662.  
  3663.           for each(redir in this.getRedirCache(browser, docURI)) {
  3664.             sites.push(redir.site);
  3665.           }
  3666.  
  3667.           
  3668.        }
  3669.        
  3670.        
  3671.        tmpPluginCount = 0;
  3672.        if (win == win.top) {
  3673.          cache = this.getExpando(win, "objectSites");
  3674.          if(cache) {
  3675.            if(this.consoleDump & LOG_CONTENT_INTERCEPT) this.dump("Adding plugin sites: " + cache.toSource());
  3676.            sites.push.apply(sites, cache);
  3677.            tmpPluginCount = cache.length;
  3678.            sites.pluginSites.push.apply(sites, cache);
  3679.          }
  3680.          this._attachPluginExtras(win);
  3681.          
  3682.          cache = this.getExpando(win, "codeSites");
  3683.          if(cache) sites.push.apply(sites, cache);
  3684.        }
  3685.        
  3686.        if (!this.getExpando(win, "contentLoaded") && (!(docShell instanceof nsIWebNavigation) || docShell.isLoadingDocument)) {
  3687.          sites.pluginCount += tmpPluginCount;
  3688.          continue;
  3689.        }
  3690.        // scripts
  3691.        this.processScriptElements(document, sites);
  3692.        
  3693.        // plugins
  3694.        this.processObjectElements(document, sites);
  3695.  
  3696.     }
  3697.     
  3698.    
  3699.     var j;
  3700.     for (j = sites.length; j-- > 0;) {
  3701.       url = sites[j];
  3702.       if (/:/.test(url) && !(
  3703.           /^[a-z]+:\/*[^\/\s]+/.test(url) || 
  3704.           /^(?:file|resource|chrome):/.test(url) // */
  3705.         )) {
  3706.         sites.splice(j, 1); // reject scheme-only URLs
  3707.       }
  3708.     }
  3709.     
  3710.     
  3711.     sites.topURL = sites[0] || '';
  3712.     return this.sortedSiteSet(sites);
  3713.     
  3714.   },
  3715.   
  3716.   findOverlay: function(browser) {
  3717.     return browser && browser.ownerDocument.defaultView.noscriptOverlay;
  3718.   },
  3719.  
  3720.   // nsIChannelEventSink implementation
  3721.  
  3722.   onChannelRedirect: function(oldChannel, newChannel, flags) {
  3723.     const rw = this.requestWatchdog;
  3724.     const policyHints = rw.extractFromChannel(oldChannel, "noscript.policyHints");
  3725.     if (policyHints) {
  3726.       // 0: aContentType, 1: aContentLocation, 2: aRequestOrigin, 3: aContext, 4: aMimeTypeGuess, 5: aInternalCall
  3727.       var uri = newChannel.URI;
  3728.       policyHints[1] = uri;
  3729.       
  3730.       var ctx = policyHints[3];
  3731.       
  3732.       if (!this.isJSEnabled(oldChannel.URI.spec)) policyHints[2] = oldChannel.URI;
  3733.       try {
  3734.         policyHints[4] = newChannel.contentType || oldChannel.contentType || policyHints[4];
  3735.       } catch(e) {}
  3736.       
  3737.       var type = policyHints[0];
  3738.       if(type != 6) { // not a document load? try to cache redirection for menus
  3739.         try {
  3740.           var site = this.getSite(uri.spec);
  3741.           var win = rw.findWindow(newChannel) || ctx && ((ctx instanceof CI.nsIDOMWindow) ? ctx : ctx.ownerDocument.defaultView); 
  3742.           var browser = win && rw.findBrowser(newChannel, win);
  3743.           if (browser) {
  3744.             this.getRedirCache(browser, win.document.documentURI)
  3745.                 .push({ site: site, type: type });
  3746.           } else {
  3747.             if (this.consoleDump) this.dump("Cannot find window for " + uri.spec);
  3748.           }
  3749.         } catch(e) {
  3750.           if (this.consoleDump) this.dump(e);
  3751.         }
  3752.       }
  3753.       if (this.shouldLoad.apply(this, policyHints) == CP_OK) { // accept
  3754.         rw.attachToChannel(newChannel, "noscript.policyHints", policyHints);
  3755.         this.resetPolicyState();
  3756.         return;
  3757.       }
  3758.       
  3759.       if (this.consoleDump) {
  3760.         this.dump("Blocked " + oldChannel.URI.spec + " -> " + uri.spec + " redirection of type " + type);
  3761.       }
  3762.       //throw NS_BINDING_ABORTED; // this lead to persistent "loading..." condition on some pages
  3763.       uri.spec = "data:application/x-noscript-blocked,";
  3764.       newChannel.loadFlags = newChannel.INHIBIT_CACHING | newChannel.LOAD_BYPASS_CACHE;
  3765.     }
  3766.   },
  3767.   
  3768.   getRedirCache: function(browser, uri) {
  3769.     var redirCache = this.getExpando(browser, "redirCache", {});
  3770.     return redirCache[uri] || (redirCache[uri] = []);
  3771.   },
  3772.   
  3773.   currentPolicyURI:null,
  3774.   currentPolicyHints: null,
  3775.   resetPolicyState: function() {
  3776.     this.currentPolicyURI = this.currentPolicyHints = null;
  3777.   },
  3778.   // nsIWebProgressListener implementation
  3779.   onLinkIconAvailable: function(x) {}, // tabbrowser.xml bug?
  3780.   onStateChange: function(wp, req, stateFlag, status) {
  3781.     if (req instanceof CI.nsIHttpChannel) {
  3782.       if (this.currentPolicyURI == req.URI) {
  3783.         this.requestWatchdog.attachToChannel(req, "noscript.policyHints",  this.currentPolicyHints);
  3784.       }
  3785.     }
  3786.     this.resetPolicyState();
  3787.     
  3788.     // handle docshell JS switching
  3789.     if ((stateFlag & STATE_START_DOC) == STATE_START_DOC && req instanceof CI.nsIChannel)
  3790.       this._handleDocJS1(wp.DOMWindow, req);
  3791.   },
  3792.   onLocationChange: function(wp, req, location) {
  3793.     try {
  3794.       if (req && (req instanceof CI.nsIChannel)) {
  3795.         this._handleDocJS2(wp.DOMWindow, req);
  3796.  
  3797.         if (this.consoleDump & LOG_JS)
  3798.           this.dump("Location Change - req.URI: " + req.URI.spec + ", window.location: " +
  3799.                   (wp.DOMWindow && wp.DOMWindow.location.href) + ", location: " + location.spec);
  3800.         this.onBeforeLoad(req, wp.DOMWindow, location);
  3801.       }
  3802.     } catch(e) {
  3803.       if (this.consoleDump) this.dump(e);
  3804.     }
  3805.   },
  3806.   onStatusChange: function() {},
  3807.   onSecurityChange: function() {}, 
  3808.   onProgressChange: function() {},
  3809.   
  3810.   // accessory hacks
  3811.   onContentSniffed: function(req) {
  3812.     try {
  3813.       if(this.consoleDump & LOG_SNIFF) {
  3814.         try {
  3815.           this.dump("OCS: " + req.URI.spec + ", " + req.contentType);
  3816.         } catch(e) {
  3817.           this.dump("OCS: " + req.URI.spec + ", CONTENT TYPE UNAVAILABLE YET");
  3818.         }
  3819.       }
  3820.       const rw = this.requestWatchdog;
  3821.       const domWindow = rw.findWindow(req);
  3822.       if(!domWindow || domWindow == domWindow.top) return;
  3823.       
  3824.       this.onBeforeLoad(req, domWindow, req.URI);
  3825.     } catch(e) {
  3826.       if (this.consoleDump) this.dump(e);
  3827.     }
  3828.   },
  3829.   onBeforeLoad: function(req, domWindow, location) {
  3830.     
  3831.     if (!domWindow) return;
  3832.     
  3833.     const uri = location;
  3834.     const rw = this.requestWatchdog;
  3835.     
  3836.     var docShell = null;
  3837.     
  3838.     if (domWindow.document && (uri.schemeIs("http") || uri.schemeIs("https"))) {
  3839.       this.filterUTF7(req, domWindow, docShell = this.domUtils.getDocShellFromWindow(domWindow)); 
  3840.     }
  3841.     
  3842.     if (this.checkJarDocument(uri, domWindow)) {
  3843.       req.cancel(NS_BINDING_ABORTED);
  3844.     }
  3845.   
  3846.    
  3847.     
  3848.     
  3849.     const topWin = domWindow == domWindow.top;
  3850.  
  3851.     var browser = null;
  3852.     var overlay = null;
  3853.     var xssInfo = null;
  3854.     
  3855.  
  3856.     if (topWin) {
  3857.       
  3858.       if (domWindow instanceof CI.nsIDOMChromeWindow) return;
  3859.     
  3860.       browser = this.domUtils.findBrowserForNode(domWindow);
  3861.       overlay = this.findOverlay(browser);
  3862.       if (overlay) {
  3863.         overlay.setMetaRefreshInfo(null, browser);
  3864.         xssInfo = rw.extractFromChannel(req, "noscript.XSS");
  3865.         if (xssInfo) xssInfo.browser = browser;
  3866.         rw.unsafeReload(browser, false);
  3867.       }
  3868.     }
  3869.     
  3870.     this._handleDocJS3(uri.spec, domWindow, docShell);
  3871.     
  3872.     var contentType;
  3873.     try {
  3874.       contentType = req.contentType;
  3875.     } catch(e) {
  3876.       contentType = "";
  3877.     }
  3878.     
  3879.     if (this.shouldLoad(7, uri, uri, domWindow, contentType, true) != CP_OK) {
  3880.       
  3881.       if (this.consoleDump & LOG_CONTENT_INTERCEPT)
  3882.         this.dump("Plugin document content type detected");
  3883.  
  3884.       if(!topWin) { 
  3885.         // check if this is an iframe
  3886.         var parentDoc = domWindow.parent.document;
  3887.         var ff = parentDoc.getElementsByTagName("iframe");
  3888.  
  3889.         for(var j = ff.length; j-- > 0;) {
  3890.           if(ff[j].contentWindow == domWindow) {
  3891.             // cause iframe placeholder
  3892.             if(this.shouldLoad(5, uri, this.siteUtils.ios.newURI(parentDoc.documentURI, null, null), ff[j], contentType, true) == CP_OK)
  3893.              return;
  3894.           }
  3895.         }
  3896.         
  3897.         if (this.consoleDump & LOG_CONTENT_BLOCK) 
  3898.           this.dump("Deferring framed plugin document");
  3899.         
  3900.         req.cancel(NS_BINDING_ABORTED);
  3901.         
  3902.         browser = browser || this.domUtils.findBrowserForNode(domWindow);
  3903.         this.getRedirCache(browser, uri.spec).push({site: this.getSite(domWindow.top.document.documentURI), type: 7});
  3904.         // defer separate embed processing for frames
  3905.         domWindow.location.href = this.createPluginDocumentURL(uri);
  3906.         return;
  3907.       }
  3908.       
  3909.       if (this.consoleDump & LOG_CONTENT_BLOCK) 
  3910.         this.dump("Blocking top-level plugin document");
  3911.  
  3912.       req.cancel(NS_BINDING_ABORTED);
  3913.       
  3914.       var embeds = domWindow.document.getElementsByTagName("embed");
  3915.       
  3916.      
  3917.       var eType = "application/x-noscript-blocked";
  3918.       var eURL = "data:" + eType + ",";
  3919.       var e;
  3920.       for (var j = embeds.length; j-- > 0;) {
  3921.         e = embeds.item(j);
  3922.         if (this.shouldLoad(5, uri, null, e, contentType, true) != CP_OK) {
  3923.           e.src = eURL;
  3924.           e.type = eType;
  3925.         }
  3926.       }
  3927.       if (xssInfo) overlay.notifyXSS(xssInfo);
  3928.       
  3929.       return;
  3930.  
  3931.     } else {
  3932.       if (topWin) {
  3933.         if (xssInfo) overlay.notifyXSSOnLoad(xssInfo);
  3934.       }
  3935.     }
  3936.  
  3937.     
  3938.   },
  3939.   
  3940.   
  3941.   _handleDocJS1: function(win, req) {
  3942.     
  3943.     const docShellJSBlocking = this.docShellJSBlocking;
  3944.     if (!docShellJSBlocking || (win instanceof CI.nsIDOMChromeWindow)) return;
  3945.     
  3946.     
  3947.     try {
  3948.       var url = req.originalURI.spec;
  3949.       
  3950.       if (!(req.loadFlags & req.LOAD_INITIAL_DOCUMENT_URI) &&
  3951.           url == "about:blank" // new tab
  3952.         ) 
  3953.         return;
  3954.       
  3955.       var jsEnabled;
  3956.       
  3957.       var docShell = this.domUtils.getDocShellFromWindow(win);
  3958.       
  3959.       if (docShellJSBlocking & 2) { // block not whitelisted
  3960.         jsEnabled = url && this.isJSEnabled(this.getSite(url)) || /^about:/.test(url);
  3961.       } else if (docShellJSBlocking & 1) { // block untrusted only
  3962.         jsEnabled = !this.isUntrusted(this.getSite(url));
  3963.       } else return;
  3964.       
  3965.       const dump = this.consoleDump & LOG_JS;
  3966.       const prevStatus = docShell.allowJavascript;
  3967.       
  3968.       // Trying to be kind with other docShell-level blocking apps (such as Tab Mix Plus), we
  3969.       // check if we're the ones who actually blocked this docShell, or if this channel is out of our control
  3970.       var prevBlocked = this.getExpando(win.document, "prevBlocked");
  3971.       prevBlocked = prevBlocked ? prevBlocked.value : "?";
  3972.  
  3973.       if (dump)
  3974.         this.dump("DocShell JS Switch: " + url + " - " + jsEnabled + "/" + prevStatus + "/" + prevBlocked);
  3975.       
  3976.       if (jsEnabled && !prevStatus) {
  3977.         
  3978.         // be nice with other blockers
  3979.         if (!prevBlocked) return;
  3980.         
  3981.         // purge body events
  3982.         try {
  3983.           var aa = win.document.body && win.document.body.attributes;
  3984.           if (aa) for (var j = aa.length; j-- > 0;) {
  3985.             if(/^on/i.test(aa[j].name)) aa[j].value = "";
  3986.           }
  3987.         } catch(e1) {
  3988.           if (this.consoleDump & LOG_JS)
  3989.             this.dump("Error purging body attributes: " + e2);
  3990.         }
  3991.       }
  3992.       
  3993.       this.requestWatchdog.attachToChannel(req, "noscript.dsjsBlocked",
  3994.                 { value: // !jsEnabled && (prevBlocked || prevStatus)
  3995.                         // we're the cause of the current disablement if
  3996.                         // we're disabling and (was already blocked by us or was not blocked)
  3997.                         !(jsEnabled || !(prevBlocked || prevStatus)) // De Morgan for the above, i.e.
  3998.                         // we're the cause of the current disablement unless
  3999.                         // we're enabling or (was already blocked by someone else = was not (blocked by us or enabled))
  4000.                         // we prefer the latter because it coerces to boolean
  4001.                 });
  4002.       
  4003.       docShell.allowJavascript = jsEnabled;
  4004.     } catch(e2) {
  4005.       if (this.consoleDump & LOG_JS)
  4006.         this.dump("Error switching DS JS: " + e2);
  4007.     }
  4008.   },
  4009.   
  4010.   _handleDocJS2: function(win, req) {
  4011.     // called at the beginning of onLocationChange
  4012.     this.setExpando(win.document,  "prevBlocked",
  4013.         this.requestWatchdog.extractFromChannel(req, "noscript.dsjsBlocked")
  4014.     );
  4015.   },
  4016.   
  4017.   _handleDocJS3: function(url, win, docShell) {
  4018.     // called at the end of onLocationChange
  4019.     if (docShell && !docShell.allowJavascript) return;
  4020.     try {
  4021.       if(this.jsHackRegExp && this.jsHack && this.jsHackRegExp.test(url) && !win._noscriptJsHack) {
  4022.         try {
  4023.           win._noscriptJsHack = true;
  4024.           win.location.href = encodeURI("javascript:try { " + this.jsHack + " } catch(e) {} void(0)");
  4025.         } catch(jsHackEx) {}
  4026.       }
  4027.     } catch(e) {}
  4028.   },
  4029.   
  4030.   beforeManualAllow: function(win) {
  4031.     // reset prevBlock info, to forcibly allow docShell JS
  4032.     this.setExpando(win.document, "prevBlock", { value: "m" });
  4033.   },
  4034.   
  4035.   checkJarDocument: function(uri, context) {
  4036.     if (this.forbidJarDocuments && (uri instanceof CI.nsIJARURI) &&
  4037.       !(/^(?:file|resource|chrome)$/.test(uri.JARFile.scheme) ||
  4038.           this.forbidJarDocumentsExceptions &&
  4039.           this.forbidJarDocumentsExceptions.test(uri.spec))
  4040.       ) {
  4041.                
  4042.       if (context && this.getPref("jarDoc.notify", true)) {
  4043.         var window = (context instanceof CI.nsIDOMWindow) && context || 
  4044.           (context instanceof CI.nsIDOMDocumentView) && context.defaultView || 
  4045.           (context instanceof CI.nsIDOMNode) && context.ownerDocument && context.ownerDocument.defaultView;
  4046.         if (window) {
  4047.           window.setTimeout(this.displayJarFeedback, 10, {
  4048.             ns: this,
  4049.             context: context,
  4050.             uri: uri.spec
  4051.           });
  4052.         } else {
  4053.           this.dump("checkJarDocument -- window not found");
  4054.         }
  4055.       }
  4056.       this.log("[NoScript] " + this.getString("jarDoc.notify", [uri.spec]));
  4057.       return true;
  4058.     }
  4059.     return false;
  4060.   },
  4061.   
  4062.   displayJarFeedback: function(info) {
  4063.     var doc = (info.context instanceof CI.nsIDOMDocument) && info.context || 
  4064.       info.context.contentDocument || info.context.document || info.context.ownerDocument;
  4065.     if (!doc) {
  4066.       this.dump("displayJarFeedback -- document not found");
  4067.       return;
  4068.     }
  4069.     var ns = info.ns;
  4070.     var browser = ns.domUtils.findBrowserForNode(doc);
  4071.     if (browser) {
  4072.       var overlay = ns.findOverlay(browser);
  4073.       if (overlay && overlay.notifyJarDocument({
  4074.           uri: info.uri,
  4075.           document: doc
  4076.       })) return;
  4077.     } else {
  4078.       this.dump("displayJarFeedback -- browser not found... falling back to content notify");
  4079.     }
  4080.     
  4081.     var message = ns.getString("jarDoc.notify", [ns.siteUtils.crop(info.uri)]) + 
  4082.       "\n\n" + ns.getString("jarDoc.notify.reference");
  4083.     
  4084.     var rootNode = doc.documentElement.body || doc.documentElement;
  4085.     const containerID = "noscript-jar-feedback";
  4086.     var container = doc.getElementById(containerID);
  4087.     if (container) container.parentNode.removeChild(container);
  4088.     container = rootNode.insertBefore(doc.createElement("div"), rootNode.firstChild || null);
  4089.     with(container.style) {
  4090.       backgroundColor = "#fffff0";
  4091.       borderBottom = "1px solid #444";
  4092.       color = "black";
  4093.       backgroundImage = "url(" + ns.pluginPlaceholder + ")";
  4094.       backgroundPosition = "left top";
  4095.       backgroundRepeat = "no-repeat";
  4096.       paddingLeft = "40px";
  4097.       margin = "0px";
  4098.       parring = "8px";
  4099.     }
  4100.     container.id = "noscript-jar-feedback";
  4101.     var description = container.appendChild(doc.createElement("pre"));
  4102.     description.appendChild(doc.createTextNode(message));
  4103.     description.innerHTML = description.innerHTML
  4104.     .replace(/\b(http:\/\/noscript.net\/faq#jar)\b/g, 
  4105.               '<a href="$1" title="NoScript JAR FAQ">$1</a>'); 
  4106.   },
  4107.   // end nsIWebProgressListener
  4108.   
  4109.   filterUTF7: function(req, window, ds) {
  4110.     try {
  4111.       var as = CC["@mozilla.org/atom-service;1"].getService(CI.nsIAtomService);
  4112.       if(window.document.characterSet == "UTF-7" ||
  4113.         !req.contentCharset && (ds.documentCharsetInfo.parentCharset + "") == "UTF-7") {
  4114.         if(this.consoleDump) this.dump("Neutralizing UTF-7 charset!");
  4115.         ds.documentCharsetInfo.forcedCharset = as.getAtom("UTF-8");
  4116.         ds.documentCharsetInfo.parentCharset = ds.documentCharsetInfo.forcedCharset;
  4117.       }
  4118.     } catch(e) { 
  4119.       if(this.consoleDump) this.dump("Error filtering charset: " + e) 
  4120.     }
  4121.   },
  4122.   
  4123.   processBrowserClick: function(ev) {
  4124.     if (this.jsEnabled || !this.getPref("fixLinks", true)) return;
  4125.     
  4126.     var a = ev.originalTarget;
  4127.     var doc = a.ownerDocument;
  4128.     if (!doc) return;
  4129.     
  4130.     var url = doc.documentURI;
  4131.     if ((!url) || this.isJSEnabled(this.getSite(url))) return;
  4132.     
  4133.     var onclick;
  4134.     
  4135.     while (!(a instanceof CI.nsIDOMHTMLAnchorElement || a instanceof CI.nsIDOMHTMLAreaElement)) {
  4136.       if (typeof(a.getAttribute) == "function" && (onclick = a.getAttribute("onclick"))) break;
  4137.       if (!(a = a.parentNode)) return;
  4138.     }
  4139.     
  4140.     const href = a.getAttribute("href");
  4141.     // fix JavaScript links
  4142.     var jsURL;
  4143.     if (href) {
  4144.       jsURL = /^javascript:/.test(href);
  4145.       if (!(jsURL || href == "#")) return;
  4146.     } else {
  4147.       jsURL = "";
  4148.     }
  4149.     
  4150.     onclick = onclick ||  a.getAttribute("onclick");
  4151.     var fixedHref = (onclick && this.extractJSLink(onclick)) || 
  4152.                      (jsURL && this.extractJSLink(href)) || "";
  4153.     
  4154.     if (fixedHref) {
  4155.       a.setAttribute("href", fixedHref);
  4156.       var title = a.getAttribute("title");
  4157.       a.setAttribute("title", title ? "[js] " + title : 
  4158.           (onclick || "") + " " + href
  4159.         );
  4160.     } else { // try processing history.go(n) //
  4161.       onclick = onclick || href;
  4162.       if(!onclick) return;
  4163.       
  4164.       jsURL = onclick.match(/history\s*\.\s*(?:go\s*\(\s*(-?\d+)\s*\)|(back|forward)\s*\(\s*)/);
  4165.       jsURL = jsURL && (jsURL = jsURL[1] || jsURL[2]) && (jsURL == "back" ? -1 : jsURL == "forward" ? 1 : jsURL); 
  4166.  
  4167.       if (!jsURL) return;
  4168.       // jsURL now has our relative history index, let's navigate
  4169.  
  4170.       var ds = this.domUtils.getDocShellFromWindow(doc.defaultView);
  4171.       if (!ds) return;
  4172.       var sh = ds.sessionHistory;
  4173.       if (!sh) return;
  4174.       
  4175.       var idx = sh.index + jsURL;
  4176.       if (idx < 0 || idx >= sh.count) return; // out of history bounds 
  4177.       ds.gotoIndex(idx);
  4178.       ev.preventDefault(); // probably not needed
  4179.     }
  4180.   },
  4181.   
  4182.   extractJSLink: function(js) {
  4183.     const findLink = /(['"])([\/\w-\?\.#%=&:@]+)\1/g;
  4184.     findLink.lastIndex = 0;
  4185.     var maxScore = -1;
  4186.     var score; 
  4187.     var m, s, href;
  4188.     while ((m = findLink.exec(js))) {
  4189.       s = m[2];
  4190.       if (/^https?:\/\//.test(s)) return s;
  4191.       score = 0;
  4192.       if (s.indexOf("/") > -1) score += 2;
  4193.       if (s.indexOf(".") > 0) score += 1;
  4194.       if (score > maxScore) {
  4195.         maxScore = score;
  4196.         href = s;
  4197.       }
  4198.     }
  4199.     return href || "";
  4200.   },
  4201.   
  4202.   createXSanitizer: function() {
  4203.     return new XSanitizer(this.filterXGetRx, this.filterXGetUserRx);
  4204.   },
  4205.   
  4206.   consoleService: CC["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService),
  4207.   
  4208.   log: function(msg) {
  4209.     this.consoleService.logStringMessage(msg);
  4210.   },
  4211.  
  4212.   dump: function(msg) {
  4213.     msg = "[NoScript] " + msg;
  4214.     dump(msg + "\n");
  4215.     if(this.consoleLog) this.log(msg);
  4216.   }
  4217. };
  4218.  
  4219.  
  4220. function XCache() {
  4221.   this._cache = {};
  4222. }
  4223.  
  4224. XCache.prototype = {
  4225.   getEntry: function(targetURI, create) {
  4226.     const key = targetURI.spec;
  4227.     return this._cache[key] || (create && (this._cache[key] = []) || null);
  4228.   },
  4229.   pickOrigin: function(targetURI, remove) {
  4230.     var entry = this.getEntry(targetURI, false);
  4231.     return entry && this.findOriginInEntry(targetURI, entry, remove);
  4232.   },
  4233.   storeOrigin: function(originURI, targetURI) {
  4234.     var entry = this.getEntry(targetURI, true);
  4235.     if (!this.findOriginInEntry(targetURI, entry)) {
  4236.       entry.push({ origin: originURI, target: targetURI });
  4237.     }
  4238.   },
  4239.   findOriginInEntry: function(targetURI, entry, remove) {
  4240.     var o;
  4241.     for (var j = entry.length; j-- > 0;) {
  4242.       o = entry[j];
  4243.       if (entry[j].target === targetURI) {
  4244.         if (remove) {
  4245.           entry.splice(j, 1);
  4246.           if (entry.length == 0) {
  4247.             delete this._cache[targetURI.spec];
  4248.           }
  4249.         }
  4250.         return o.origin;
  4251.       }
  4252.     }
  4253.     return null;
  4254.   }
  4255. };
  4256.  
  4257. function RequestInfo(channel, url, origin, window) {
  4258.   this.channel = channel;
  4259.   this.sanitizedURI = url;
  4260.   this.window = window;
  4261.   this.unsafeRequest = {
  4262.     URI: url.clone(),
  4263.     postData: null,
  4264.     referrer: channel.referrer && channel.referrer.clone(),
  4265.     origin: origin,
  4266.     loadFlags: channel.loadFlags,
  4267.     issued: false,
  4268.     window: null
  4269.   }
  4270. }
  4271. RequestInfo.prototype = {
  4272.   xssMaybe: false 
  4273. }
  4274.  
  4275. function nsISupportWrapper(wrapped) {
  4276.   this.wrappedJSObject = wrapped;
  4277. }
  4278. nsISupportWrapper.prototype = {
  4279.   QueryInterface: xpcom_generateQI([CI.nsISupports])
  4280. }
  4281.  
  4282. function RequestWatchdog(ns) {
  4283.   this.ns = ns;
  4284.   this.siteUtils = ns.siteUtils;
  4285.   this.dns = CC["@mozilla.org/network/dns-service;1"]
  4286.                   .getService(CI.nsIDNSService);
  4287.   this.LOAD_DOCUMENT_URI = CI.nsIChannel.LOAD_DOCUMENT_URI;
  4288. }
  4289.  
  4290. RequestWatchdog.prototype = {
  4291.   ns: null,
  4292.   dns: null,
  4293.   callback: null,
  4294.   externalLoad: null,
  4295.   noscriptReload: null,
  4296.   LOAD_DOCUMENT_URI: CI.nsIChannel.LOAD_DOCUMENT_URI,
  4297.   
  4298.   get dummyPost() {
  4299.     var dummyPost = CC["@mozilla.org/io/string-input-stream;1"].createInstance();
  4300.     dummyPost.setData("", 0);
  4301.     this.__defineGetter__("dummyPost", function() { return dummyPost; });
  4302.     return dummyPost;
  4303.   },
  4304.   
  4305.   QueryInterface: xpcom_generateQI([CI.nsIObserver, CI.nsISupportsWeakReference, CI.nsISupports]),
  4306.   
  4307.   getUnsafeRequest: function(browser) {
  4308.     return this.ns.getExpando(browser, "unsafeRequest");
  4309.   },
  4310.   setUnsafeRequest: function(browser, request) {
  4311.     return this.ns.setExpando(browser, "unsafeRequest", request);
  4312.   },
  4313.   
  4314.   
  4315.   unsafeReload: function(browser, start) {
  4316.     this.ns.setExpando(browser, "unsafeReload", start);
  4317.     if (start) {
  4318.       const unsafeRequest = this.getUnsafeRequest(browser);
  4319.       if (unsafeRequest) {
  4320.         // should we figure out what to do with unsafeRequest.loadFlags?
  4321.         var wn = browser.webNavigation;
  4322.         if(unsafeRequest.window) {
  4323.           // a subframe...
  4324.           try {
  4325.             wn = this.ns.domUtils.getDocShellFromWindow(unsafeRequest.window).QueryInterface(CI.nsIWebNavigation);
  4326.           } catch(ex) {
  4327.             this.ns.dump(ex);
  4328.           }
  4329.           unsafeRequest.window = null;
  4330.         }
  4331.        
  4332.         wn.loadURI(unsafeRequest.URI.spec, 
  4333.               wn.LOAD_FLAGS_BYPASS_CACHE | 
  4334.               wn.LOAD_FLAGS_IS_REFRESH,
  4335.               unsafeRequest.referrer, unsafeRequest.postData, null);
  4336.         unsafeRequest.issued = true;
  4337.       } else {
  4338.         browser.reload();
  4339.       }
  4340.     }
  4341.     return start;
  4342.   },
  4343.  
  4344.   isUnsafeReload: function(browser) {
  4345.     return this.ns.getExpando(browser, "unsafeReload");
  4346.   },
  4347.   
  4348.   resetUntrustedReloadInfo: function(browser, channel) {
  4349.     if (!browser) return;
  4350.     var window = this.findWindow(channel);
  4351.     if (browser.contentWindow == window) {
  4352.       if (this.ns.consoleDump) this.dump(channel, "Top level document, resetting former untrusted browser info");
  4353.       this.setUntrustedReloadInfo(browser, false);
  4354.     }
  4355.   },
  4356.   setUntrustedReloadInfo: function(browser, status) {
  4357.     return this.ns.setExpando(browser, "untrustedReload", status);
  4358.   },
  4359.   getUntrustedReloadInfo: function(browser) {
  4360.     return this.ns.getExpando(browser, "untrustedReload");
  4361.   },
  4362.   
  4363.   extractInternalReferrer: function(channel) {
  4364.     if (channel instanceof CI.nsIPropertyBag2) try {
  4365.       return channel.getPropertyAsInterface("docshell.internalReferrer", CI.nsIURL);
  4366.     } catch(e) {}
  4367.     return null;
  4368.   },
  4369.   extractInternalReferrerSpec: function(channel) {
  4370.     var ref = this.extractInternalReferrer(channel);
  4371.     return ref && ref.spec || null;
  4372.   },
  4373.   
  4374.   detectBackFrame: function(prev, next, ds) {
  4375.     if (prev.ID != next.ID) return prev.URI.spec;
  4376.     if ((prev instanceof CI.nsISHContainer) &&
  4377.        (next instanceof CI.nsISHContainer) &&
  4378.        (ds instanceof CI.nsIDocShellTreeNode)
  4379.       ) {
  4380.       var uri;
  4381.       for (var j = Math.min(prev.childCount, next.childCount, ds.childCount); j-- > 0;) {
  4382.         uri = this.detectBackFrame(prev.GetChildAt(j),
  4383.                                    next.GetChildAt(j),
  4384.                                    ds.GetChildAt(j));
  4385.         if (uri) return uri.spec;
  4386.       }
  4387.     }
  4388.     return null;
  4389.   },
  4390.   
  4391.   traceBackHistory: function(sh, window, breadCrumbs) {
  4392.     var wantsBreadCrumbs = !breadCrumbs;
  4393.     breadCrumbs = breadCrumbs || [window.document.documentURI];
  4394.     
  4395.     var he;
  4396.     var uri = null;
  4397.     var site = '';
  4398.     for (var j = sh.index; j > -1; j--) {
  4399.        he = sh.getEntryAtIndex(j, false);
  4400.        if (he.isSubFrame && j > 0) {
  4401.          uri = this.detectBackFrame(sh.getEntryAtIndex(j - 1), h,
  4402.            this.ns.domUtils.getDocShellFromWindow(window)
  4403.          );  
  4404.        } else {
  4405.         // not a subframe navigation 
  4406.         if (window == window.top) {
  4407.           uri = he.URI.spec; // top frame, return history entry
  4408.         } else {
  4409.           window = window.parent;
  4410.           uri = window.document.documentURI;
  4411.         }
  4412.       }
  4413.       if (!uri) break;
  4414.       if (breadCrumbs[0] && breadCrumbs[0] == uri) continue;
  4415.       breadCrumbs.unshift(uri);
  4416.       var site = this.ns.getSite(uri);
  4417.       if (site) break;
  4418.     }
  4419.     return wantsBreadCrumbs ? breadCrumbs : site;
  4420.   },
  4421.   
  4422.   traceBack: function(channel, breadCrumbs) {
  4423.     try {
  4424.       var window = this.findWindow(channel);
  4425.       if (window instanceof CI.nsIInterfaceRequestor) {
  4426.         var webNav = window.getInterface(CI.nsIWebNavigation);
  4427.         const sh = webNav.sessionHistory;
  4428.         return sh ? this.traceBackHistory(sh, window, breadCrumbs || null) 
  4429.                   : webNav.currentURI && !webNav.currentURI.equals(channel.URI) 
  4430.                     ? webNav.currentURI.spec
  4431.                     : '';
  4432.       }
  4433.     } catch(e) {
  4434.       if (this.ns.consoleDump) this.dump(channel, "Error tracing back origin: " + e.message);
  4435.     }
  4436.     return '';
  4437.   },
  4438.   
  4439.   observe: function(subject, topic, data) {
  4440.     if((this.ns.consoleDump & LOG_SNIFF) && (subject instanceof CI.nsIHttpChannel)) {
  4441.       this.ns.dump(topic + ": " + subject.URI.spec + ", " + subject.loadFlags);
  4442.     }
  4443.     if (!((subject instanceof CI.nsIHttpChannel) && (subject.loadFlags & this.LOAD_DOCUMENT_URI))) return;
  4444.     switch(topic) {
  4445.       case "http-on-modify-request":
  4446.         try {
  4447.           this.filterXSS(subject);
  4448.         } catch(e) {
  4449.           this.abort({ channel: subject, reason: e + " --- " + e.stack, silent: true });
  4450.         }
  4451.         break;
  4452.       case "http-on-examine-response":
  4453.         this.ns.onContentSniffed(subject);
  4454.       break;
  4455.     }
  4456.   },
  4457.   
  4458.  
  4459.   isHome: function(url) {
  4460.     return url instanceof CI.nsIURL &&
  4461.       this.getHomes().some(function(urlSpec) {
  4462.         try {
  4463.           return !url.getRelativeSpec(SiteUtils.ios.newURI(urlSpec, null, null));
  4464.         } catch(e) {}
  4465.         return false;
  4466.       });
  4467.   },
  4468.   getHomes: function(pref) {
  4469.     var homes;
  4470.     try {
  4471.       homes = this.ns.prefService.getComplexValue(pref || "browser.startup.homepage",
  4472.                          CI.nsIPrefLocalizedString).data;
  4473.     } catch (e) {
  4474.       return pref ? [] : this.getHomes("browser.startup.homepage.override");
  4475.     }
  4476.     return homes ? homes.split("|") : [];
  4477.   },
  4478.   
  4479.   checkWindowName: function(window) {
  4480.     var originalAttempt = window.name;
  4481.       
  4482.     if (/[%=\(\\]/.test(originalAttempt) && InjectionChecker.checkJS(originalAttempt)) {
  4483.       window.name = originalAttempt.replace(/[%=\(\\]/g, " ");
  4484.     }
  4485.     if (originalAttempt.length > 11) {
  4486.       try {
  4487.         if ((originalAttempt.length % 4 == 0)) { 
  4488.           var bin = window.atob(window.name);
  4489.           if(/[=\(\\]/.test(bin) && InjectionChecker.checkJS(bin)) {
  4490.             window.name = "BASE_64_XSS";
  4491.           }
  4492.         }
  4493.       } catch(e) {}
  4494.     }
  4495.     if (originalAttempt != window.name) {
  4496.       this.ns.log('[NoScript XSS]: sanitized window.name, "' + originalAttempt + '" to "' + window.name + '".');
  4497.     }
  4498.   },
  4499.   
  4500.   filterXSS: function(channel) {
  4501.     
  4502.     const ns = this.ns;
  4503.     const url = channel.URI;
  4504.     const originalSpec = url.spec;
  4505.  
  4506.     const xorigin = ns.xcache.pickOrigin(url, true); // picks and remove cached entry
  4507.     
  4508.     if (this.noscriptReload == originalSpec) {
  4509.       // fast cache route for NoScript-triggered reloads
  4510.       this.noscriptReload = null;
  4511.       try {
  4512.         if (ns.consoleDump) {
  4513.           this.dump(channel, "Fast reload, original flags: " + 
  4514.             channel.loadFlags + ", " + (channel.loadGroup && channel.loadGroup.loadFlags));
  4515.         }
  4516.         channel.loadFlags = (channel.loadFlags & ~CI.nsIChannel.VALIDATE_ALWAYS) | 
  4517.                     CI.nsIChannel.LOAD_FROM_CACHE | CI.nsIChannel.VALIDATE_NEVER;
  4518.         if (channel.loadGroup) {
  4519.           channel.loadGroup.loadFlags = (channel.loadGroup.loadFlags & ~CI.nsIChannel.VALIDATE_ALWAYS) | 
  4520.                   CI.nsIChannel.LOAD_FROM_CACHE | CI.nsIChannel.VALIDATE_NEVER;
  4521.         }
  4522.         if (ns.consoleDump) {
  4523.           this.dump(channel, "Fast reload, new flags: " + 
  4524.             channel.loadFlags + ", " + (channel.loadGroup && channel.loadGroup.loadFlags));
  4525.         }
  4526.       } catch(e) {
  4527.         // we may have a problem here due to something Firekeeper 0.2.11 started doing..
  4528.         ns.dump(e);
  4529.       }
  4530.     }
  4531.     
  4532.    
  4533.     var browser = null;
  4534.     var window = null;
  4535.     
  4536.     var origin = xorigin && xorigin.spec || 
  4537.         channel.originalURI.spec != originalSpec && channel.originalURI.spec 
  4538.         || this.extractInternalReferrerSpec(channel) || null;
  4539.  
  4540.     var untrustedReload = false;
  4541.    
  4542.     var originSite = null;
  4543.     
  4544.     if (!origin) {
  4545.       if ((channel instanceof CI.nsIHttpChannelInternal) && channel.documentURI) {
  4546.         if (originalSpec == channel.documentURI.spec) {
  4547.            var breadCrumbs = [originalSpec];
  4548.            originSite = this.traceBack(channel, breadCrumbs);
  4549.            if (originSite) {
  4550.              origin = breadCrumbs.join(">>>");
  4551.              if (ns.consoleDump) this.dump(channel, "TRACEBACK ORIGIN: " + originSite + " FROM " + origin);
  4552.            } else {
  4553.              // check untrusted reload
  4554.              browser = this.findBrowser(channel);
  4555.              if (!this.getUntrustedReloadInfo(browser)) {
  4556.                if (ns.consoleDump) this.dump(channel, "Trusted reload");
  4557.                return;
  4558.              }
  4559.              origin = "";
  4560.              untrustedReload = true;
  4561.              if (ns.consoleDump) this.dump(channel, "Untrusted reload");
  4562.            }
  4563.         } else {
  4564.           origin = channel.documentURI.spec;
  4565.           if (ns.consoleDump) this.dump(channel, "ORIGIN (from channel.documentURI): " + origin);
  4566.         }
  4567.       } else {
  4568.         if (ns.consoleDump) this.dump(channel, "***** NO ORIGIN CAN BE INFERRED!!! *****");
  4569.       }
  4570.     } else {
  4571.       if (channel.loadFlags & channel.LOAD_INITIAL_DOCUMENT_URI && channel.originalURI.spec == channel.URI.spec) {
  4572.         // clean up after user action
  4573.         window = window || this.findWindow(channel);
  4574.         browser = browser || this.findBrowser(channel, window);
  4575.         this.resetUntrustedReloadInfo(browser, channel);
  4576.         var unsafeRequest = this.getUnsafeRequest(browser);
  4577.         if (unsafeRequest && unsafeRequest.URI.spec != channel.originalURI.spec && 
  4578.             (!window || window == window.top || window == unsafeRequest.window)) {
  4579.           this.setUnsafeRequest(browser, null);
  4580.         }
  4581.       }
  4582.       if (ns.consoleDump) this.dump(channel, "ORIGIN: " + origin + ", xorigin: " + (xorigin && xorigin.spec) + ", originalURI: " + channel.originalURI.spec);
  4583.     }
  4584.     
  4585.     const su = this.siteUtils;
  4586.     originSite = originSite || su.getSite(origin);
  4587.     
  4588.     var host = channel.URI.host;
  4589.     if (host[host.length - 1] == "." && this.ns.getPref("canonicalFQDN", true)) {
  4590.       try {
  4591.         channel.URI.host = this.dns.resolve(host, 2).canonicalName;
  4592.       } catch(ex) {
  4593.         this.dump(channel, ex);
  4594.       }
  4595.     }
  4596.     
  4597.    
  4598.     var targetSite;
  4599.     const globalJS = ns.globalJS;
  4600.     
  4601.     if(!globalJS) {
  4602.       if(ns.autoAllow) {
  4603.         window = window || this.findWindow(channel);
  4604.         if (window && window == window.top) {
  4605.           targetSite = ns.getQuickSite(originalSpec, ns.autoAllow);
  4606.           if(targetSite && !ns.isJSEnabled(targetSite)) {
  4607.             ns.autoTemp(targetSite);
  4608.           }
  4609.           targetSite = su.getSite(originalSpec);
  4610.         }
  4611.       }
  4612.       if(!targetSite) {
  4613.         targetSite = su.getSite(originalSpec);
  4614.         if(!ns.isJSEnabled(targetSite)) {
  4615.           if (ns.checkShorthands(targetSite)) {
  4616.              ns.autoTemp(targetSite);
  4617.           } else { 
  4618.             if (ns.consoleDump) this.dump(channel, "Destination " + originalSpec + " is noscripted, SKIP");
  4619.             return;
  4620.           }
  4621.         }
  4622.       }
  4623.     }
  4624.      // fast return if nothing to do here
  4625.     if (!(ns.filterXPost || ns.filterXGet)) return; 
  4626.     
  4627.     if(!targetSite) targetSite = su.getSite(originalSpec);
  4628.     
  4629.     // noscript.injectionCheck about:config option adds first-line 
  4630.     // detection for XSS injections in GET requests originated by 
  4631.     // whitelisted sites and landing on top level windows. Value can be:
  4632.     // 0 - never check
  4633.     // 1 - check cross-site requests from temporary allowed sites
  4634.     // 2 - check every cross-site request (default)
  4635.     // 3 - check every request
  4636.     
  4637.     var injectionCheck = ns.injectionCheck;
  4638.     
  4639.     if (originSite == targetSite && 
  4640.        (injectionCheck < 3 || channel.requestMethod != "GET") 
  4641.       ) return; // same origin, fast return
  4642.     
  4643.     if (this.callback && this.callback(channel, origin)) return;
  4644.     
  4645.     
  4646.     var externalLoad = this.externalLoad && this.externalLoad == originalSpec;
  4647.     if (externalLoad) {
  4648.       this.externalLoad = null;
  4649.     } else if(this.isUnsafeReload(browser = browser || this.findBrowser(channel))) {
  4650.       if (ns.consoleDump) this.dump(channel, "UNSAFE RELOAD of [" + originalSpec +"] from [" + origin + "], SKIP");
  4651.       return;
  4652.     }
  4653.     
  4654.     
  4655.     
  4656.     
  4657.     if (ns.filterXExceptions) {
  4658.       try {
  4659.         if (ns.filterXExceptions.test(decodeURI(originalSpec)) &&
  4660.             !this.isBadException(host)
  4661.             ) {
  4662.           // "safe" xss target exception
  4663.           if (ns.consoleDump) this.dump(channel, "Safe target according to filterXExceptions: " + ns.filterXExceptions.toString());
  4664.           return;
  4665.         }
  4666.       } catch(e) {}
  4667.     }
  4668.     
  4669.     
  4670.     if (!originSite) { // maybe data or javascript URL?
  4671.       if (/^(?:javascript|data):/i.test(origin) && ns.getPref("xss.trustData", true)) {
  4672.         var breadCrumbs = [origin];
  4673.         originSite = this.traceBack(channel, breadCrumbs);
  4674.         if (originSite) { 
  4675.           origin = breadCrumbs.join(">>>");
  4676.         }
  4677.         delete breadCrumbs;
  4678.       }
  4679.     }
  4680.     
  4681.     var originalAttempt;
  4682.     var injectionAttempt = false;
  4683.     window = window || this.findWindow(channel);
  4684.     
  4685.     // neutralize window.name-based attack
  4686.     if (window && window.name) {
  4687.       this.checkWindowName(window);
  4688.     }
  4689.    
  4690.     if (globalJS || ns.isJSEnabled(originSite)) {
  4691.       this.resetUntrustedReloadInfo(browser = browser || this.findBrowser(channel, window), channel);
  4692.       
  4693.       
  4694.       injectionAttempt = injectionCheck && (injectionCheck > 1 || ns.isTemp(originSite)) &&
  4695.         (!window || ns.injectionCheckSubframes || window == window.top) &&
  4696.         ns.injectionChecker.checkURL(originalSpec);
  4697.       
  4698.       if (injectionAttempt) {
  4699.         if (ns.consoleDump) this.dump(channel, "Detected injection attempt at level " + injectionCheck);
  4700.       } else {
  4701.         if (ns.consoleDump) this.dump(channel, "externalLoad flag is " + externalLoad);
  4702.  
  4703.         if (externalLoad) { // external origin ?
  4704.           if (ns.consoleDump) this.dump(channel, "External load from " + origin);
  4705.           if (this.isHome(url)) {
  4706.             if (ns.consoleDump) this.dump(channel, "Browser home page, SKIP");
  4707.             return;
  4708.           }
  4709.           if (ns.getPref("xss.trustExternal", false)) {
  4710.             if (ns.consoleDump) this.dump(channel, "noscript.xss.trustExternal is TRUE, SKIP");
  4711.             return;
  4712.           }
  4713.           origin = "///EXTERNAL///";
  4714.           originSite = "";
  4715.         } else if(ns.getPref("xss.trustTemp", true) || !ns.isTemp(originSite)) { // temporary allowed origin?
  4716.           if (ns.consoleDump) {
  4717.             this.dump(channel, "Origin " + origin + " is trusted, SKIP");
  4718.           }
  4719.           return;
  4720.         }
  4721.         if (ns.consoleDump) 
  4722.           this.dump(channel, (externalLoad ? "External origin" : "Origin " + origin + " is TEMPORARILY allowed") + 
  4723.             ", we don't really trust it");
  4724.       }
  4725.     }
  4726.     
  4727.     if (untrustedReload && browser) {
  4728.       this.resetUntrustedReloadInfo(browser, channel);
  4729.     }
  4730.  
  4731.     // -- DANGER ZONE --
  4732.     
  4733.     var requestInfo = new RequestInfo(channel, url, origin, window);
  4734.  
  4735.     // transform upload requests into no-data GETs
  4736.     if (ns.filterXPost && (channel instanceof CI.nsIUploadChannel) && channel.uploadStream
  4737.       && !injectionAttempt // this will rule out the possibility we strip trusted to trusted uploads
  4738.       ) {
  4739.       channel.requestMethod = "GET";
  4740.       requestInfo.unsafeRequest.postData = channel.uploadStream;
  4741.       channel.setUploadStream(this.dummyUpload, "", -1);
  4742.       this.notify(this.addXssInfo(requestInfo, {
  4743.         reason: "filterXPost",
  4744.         originalAttempt: originalSpec,
  4745.         silent: untrustedReload
  4746.       }));
  4747.     }
  4748.     
  4749.     if (ns.filterXGet && ns.filterXGetRx) {
  4750.       var changes = null;
  4751.       var xsan = ns.createXSanitizer();
  4752.       // sanitize referrer
  4753.       if (channel.referrer && channel.referrer.spec) {
  4754.         originalAttempt = channel.referrer.spec;
  4755.         xsan.brutal = true;
  4756.         try {
  4757.           if (channel.referrer instanceof CI.nsIURL) {
  4758.             changes = xsan.sanitizeURL(channel.referrer);
  4759.           } else {
  4760.             channel.referrer.spec =  xsan.sanitizeURIComponent(originalAttempt);
  4761.           }
  4762.         } catch(e) {
  4763.           this.dump("Failed sanitizing referrer " + channel.referrer.spec + ", " + e);
  4764.           channel.referrer.spec = "";
  4765.         }
  4766.         try {
  4767.           if (!changes) {
  4768.             changes = { 
  4769.               minor: !channel.referrer.spec || 
  4770.                      decodeURI(originalAttempt) != decodeURI(channel.referrer.spec) 
  4771.             };
  4772.           }
  4773.           if (changes.minor) {
  4774.             channel.referrer = channel.referrer.clone();
  4775.             this.notify(this.addXssInfo(requestInfo, {
  4776.               reason: "filterXGetRef",
  4777.               originalAttempt: url.spec + " (REF: " + originalAttempt + ")",
  4778.               silent: true,
  4779.               sanitizedURI: channel.referrer
  4780.             }));
  4781.           }
  4782.         } catch(e) {
  4783.           this.dump("Failed notifying referrer sanitization: " + channel.referrer.spec + ", " + e);
  4784.           channel.referrer.spec = "";
  4785.           channel.referrer = channel.referrer.clone();
  4786.         }
  4787.       }
  4788.       
  4789.       originalAttempt = url.spec;
  4790.       xsan.brutal = injectionAttempt;
  4791.       changes = xsan.sanitizeURL(url);
  4792.       if (changes.minor) {
  4793.         this.proxyHack(channel);
  4794.         this.notify(this.addXssInfo(requestInfo, {
  4795.           reason: "filterXGet",
  4796.           originalAttempt: originalAttempt,
  4797.           silent: !changes.major 
  4798.         }));
  4799.       }
  4800.     }
  4801.    
  4802.     
  4803.  
  4804.     if (requestInfo.xssMaybe) {
  4805.       // avoid surprises from history & cache
  4806.       if (channel instanceof CI.nsICachingChannel) {
  4807.         
  4808.         const CACHE_FLAGS = channel.LOAD_FROM_CACHE | 
  4809.                             channel.VALIDATE_NEVER | 
  4810.                             channel.LOAD_ONLY_FROM_CACHE;
  4811.         // if(channel.loadFlags & CACHE_FLAGS) {
  4812.           channel.loadFlags = channel.loadFlags & ~CACHE_FLAGS | channel.LOAD_BYPASS_CACHE;
  4813.           if (this.consoleDump) this.dump(channel, "SKIPPING CACHE");
  4814.         // }
  4815.       }
  4816.       
  4817.       if (requestInfo.window && 
  4818.           (requestInfo.window == requestInfo.window.top || 
  4819.           requestInfo.window == requestInfo.unsafeRequest.window)
  4820.         ) {
  4821.         this.setUnsafeRequest(requestInfo.browser, requestInfo.unsafeRequest);
  4822.       }
  4823.     }
  4824.   },
  4825.   
  4826.   isBadException: function(host) {
  4827.     // TLD check for google search
  4828.     var m = host.match(/\bgoogle\.((?:[a-z]{1,3}\.)?[a-z]+)$/i);
  4829.     return m && this.ns.getPublicSuffix(host) != m[1];
  4830.   },
  4831.   
  4832.   proxyHack: function(channel) {
  4833.     // Work-around for channel.URI not being used directly here:
  4834.     // http://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpChannel.cpp#504
  4835.     
  4836.     var proxyInfo = CI.nsIProxiedChannel && (channel instanceof CI.nsIProxiedChannel) 
  4837.       ? channel.proxyInfo
  4838.       : Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
  4839.           .getService(Components.interfaces.nsIProtocolProxyService)
  4840.           .resolve(channel.URI, 0);
  4841.      if (proxyInfo && proxyInfo.type == "http") {
  4842.        if (channel.URI.userPass == "") {
  4843.          channel.URI.userPass = "xss:xss";
  4844.          // resetting this bit will avoid auth confirmation prompt
  4845.          channel.loadFlags = channel.loadFlags & ~channel.LOAD_INITIAL_DOCUMENT_URI;
  4846.        }
  4847.      }
  4848.   },
  4849.   
  4850.   abort: function(requestInfo) {
  4851.     var channel = requestInfo.channel;
  4852.     if (channel instanceof CI.nsIRequest) {
  4853.       
  4854.       channel.cancel(NS_BINDING_ABORTED);
  4855.       /*
  4856.       if (channel instanceof CI.nsIRequestObserver) try {
  4857.         channel.onStopRequest(channel, null, NS_BINDING_ABORTED);
  4858.       } catch(e) {}
  4859.       if (channel.loadGroup) try { 
  4860.         channel.loadGroup.removeRequest(channel, null, NS_BINDING_ABORTED);
  4861.       } catch(e) {}
  4862.       */
  4863.     }
  4864.     this.dump(channel, "Aborted - " + requestInfo.reason);
  4865.  
  4866.     this.notify(requestInfo);
  4867.   },
  4868.   
  4869.   mergeDefaults: function(o1, o2) {
  4870.     for (p in o2) {
  4871.       if (!(p in o1)) o1[p] = o2[p];
  4872.     }
  4873.     return o1;
  4874.   },
  4875.   
  4876.   addXssInfo: function(requestInfo, xssInfo) {
  4877.     try {
  4878.       requestInfo.window = requestInfo.window || this.findWindow(requestInfo.channel);
  4879.       requestInfo.browser = requestInfo.browser || (requestInfo.window && 
  4880.                             this.ns.domUtils.findBrowserForNode(requestInfo.window));
  4881.     } catch(e) {}
  4882.     requestInfo.xssMaybe = true;
  4883.     return this.mergeDefaults(xssInfo, requestInfo);
  4884.   },
  4885.   
  4886.   notify: function(requestInfo) {
  4887.     var msg = "[NoScript XSS] " + this.ns.getString("xss.reason." + requestInfo.reason, [ 
  4888.         requestInfo.originalAttempt || "N/A",
  4889.         requestInfo.unsafeRequest && requestInfo.unsafeRequest.origin || "",
  4890.         requestInfo.sanitizedURI && requestInfo.sanitizedURI.spec || ""
  4891.       ]);
  4892.     this.dump(requestInfo.channel, "Notifying " + msg + "\n\n\n");
  4893.     this.ns.log(msg);
  4894.    
  4895.     try {
  4896.       if (requestInfo.silent || !requestInfo.window || !this.ns.getPref("xss.notify", true)) 
  4897.         return;
  4898.       if(requestInfo.window != requestInfo.window.top) { 
  4899.         // subframe
  4900.  
  4901.         var cur = this.getUnsafeRequest(requestInfo.browser);
  4902.         if(cur && !cur.issued) return;
  4903.         
  4904.         requestInfo.unsafeRequest.window = requestInfo.window;
  4905.         this.observeSubframeXSS(requestInfo.originalAttempt, requestInfo.unsafeRequest);
  4906.         
  4907.         if(!this.ns.getPref("xss.notify.subframes", true))
  4908.           return;
  4909.  
  4910.         var overlay = this.ns.findOverlay(requestInfo.browser);
  4911.         if(overlay) overlay.notifyXSS(requestInfo);
  4912.       }
  4913.       this.attachToChannel(requestInfo.channel, "noscript.XSS", requestInfo);
  4914.     } catch(e) {
  4915.       dump(e + "\n");
  4916.     }
  4917.   },
  4918.   
  4919.   observeSubframeXSS: function(url, unsafeRequest) {
  4920.     unsafeRequest.window.addEventListener("unload", function(ev) {
  4921.         var w = ev.currentTarget;
  4922.         if(w.location.href != url) return; 
  4923.         w.removeEventListener("unload", arguments.callee, false);
  4924.         unsafeRequest.window = null;
  4925.      }, false);
  4926.   },
  4927.   
  4928.   attachToChannel: function(channel, key, requestInfo) {
  4929.     if (channel instanceof CI.nsIWritablePropertyBag2) 
  4930.       channel.setPropertyAsInterface(key, new nsISupportWrapper(requestInfo));
  4931.   },
  4932.   extractFromChannel: function(channel, key, preserve) {
  4933.     if (channel instanceof CI.nsIPropertyBag2) {
  4934.       try {
  4935.         var requestInfo = channel.getPropertyAsInterface(key, CI.nsISupports);
  4936.         if (requestInfo) {
  4937.           if(!preserve && (channel instanceof CI.nsIWritablePropertyBag)) channel.deleteProperty(key);
  4938.           return requestInfo.wrappedJSObject;
  4939.         }
  4940.       } catch(e) {}
  4941.     }
  4942.     return null;
  4943.   },
  4944.   
  4945.   findWindow: function(channel) {
  4946.     try {
  4947.       return channel.notificationCallbacks.QueryInterface(
  4948.         CI.nsIInterfaceRequestor).getInterface(
  4949.         CI.nsIDOMWindow);
  4950.     } catch(e) {
  4951.       return null;
  4952.     }
  4953.   },
  4954.   findBrowser: function(channel, window) {
  4955.     var w = window || this.findWindow(channel);
  4956.     return w && this.ns.domUtils.findBrowserForNode(w);
  4957.   },
  4958.   
  4959.   dump: function(channel, msg) {
  4960.     if (!(this.ns.consoleDump & LOG_XSS_FILTER)) return;
  4961.     dump("[NoScript] ");
  4962.     dump((channel.URI && channel.URI.spec) || "null URI?" );
  4963.     if (channel.originalURI && channel.originalURI.spec != channel.URI.spec) {
  4964.       dump(" (" + channel.originalURI.spec + ")");
  4965.     }
  4966.     dump(" *** ");
  4967.     dump(msg);
  4968.     dump("\n");
  4969.   }
  4970.   
  4971.   
  4972. }
  4973.  
  4974.  
  4975. var Entities = {
  4976.   _htmlNode: null,
  4977.  
  4978.  
  4979.   get htmlNode() {
  4980.     return this._htmlNode || (this._htmlNode =
  4981.       (function() {
  4982.         try {
  4983.           // we need a loose HTML node, only way to get it today seems using hidden window
  4984.           var as = CC["@mozilla.org/appshell/appShellService;1"].getService(CI.nsIAppShellService);
  4985.           as.hiddenDOMWindow.addEventListener("unload", function(ev) {
  4986.             ev.currentTarget.removeEventListener("unload", arguments.callee, false);
  4987.             Entities._htmlNode = null;
  4988.             doc = null;
  4989.             // dump("*** Free Entities._htmlNode ***\n");
  4990.           }, false);
  4991.           return as.hiddenDOMWindow.document.createElement("body");
  4992.         } catch(e) {
  4993.           dump("[NoSript Entities]: Cannot grab an HTML node, falling back to XHTML... " + e + "\n");
  4994.           return CC["@mozilla.org/xul/xul-document;1"]
  4995.             .createInstance(CI.nsIDOMDocument)
  4996.             .createElementNS("http://www.w3.org/1999/xhtml", "body")
  4997.         }
  4998.       })()
  4999.       );
  5000.   },
  5001.   convert: function(e) {
  5002.     try {
  5003.       this.htmlNode.innerHTML = e;
  5004.       var child = this.htmlNode.firstChild || null;
  5005.       return child && child.nodeValue || e;
  5006.     } catch(ex) {
  5007.       return e;
  5008.     }
  5009.   },
  5010.   convertAll: function(s) {
  5011.     return s.replace(/[\\&][^<>]+/g, function(e) { return Entities.convert(e) });
  5012.   },
  5013.   convertDeep: function(s) {
  5014.     for (var prev = null; (s = this.convertAll(s)) != prev; prev = s);
  5015.     return s;
  5016.   },
  5017.   neutralize: function(e, whitelist) {
  5018.     var c = this.convert(e);
  5019.     return (c == e) ? c : (whitelist && whitelist.test(c) ? e : e.replace(";", ","));
  5020.   },
  5021.   neutralizeAll: function(s, whitelist) {
  5022.     return s.replace(/&[\w#-]*?;/g, function(e) { return Entities.neutralize(e, whitelist || null); });
  5023.   }
  5024. };
  5025.  
  5026. function SyntaxChecker() {
  5027.   this.sandbox = new Components.utils.Sandbox("about:");
  5028. }
  5029.  
  5030. SyntaxChecker.prototype = {
  5031.   lastError: null,
  5032.   lastFunction: null,
  5033.   check: function(script) {
  5034.     this.sandbox.script = script;
  5035.      try {
  5036.        return !!(this.lastFunction = Components.utils.evalInSandbox("new Function(script)", this.sandbox));
  5037.      } catch(e) {
  5038.        this.lastError = e;
  5039.      }
  5040.      return false;
  5041.   },
  5042.   unquote: function(s, q) {
  5043.     if (!(s[0] == q && s[s.length - 1] == q &&
  5044.         !s.replace(/\\./g, '').replace(/^(['"])[^\n\r]*?\1/, "")
  5045.       )) return null;
  5046.     try {
  5047.       return Components.utils.evalInSandbox(s, this.sandbox);
  5048.     } catch(e) {}
  5049.     return null;
  5050.   }
  5051. };
  5052.  
  5053. function fuzzify(s) {
  5054.   return s.replace(/\w/g, '\\W*$&');
  5055. }
  5056.  
  5057. const IC_WINDOW_OPENER_PATTERN = fuzzify("alert|confirm|prompt|open|print");
  5058. const IC_EVENT_PATTERN = fuzzify("on(?:load|page|unload|ready|error|focus|blur|mouse)") + "(?:\\W*[a-z])*";
  5059. const IC_EVENT_DOS_PATTERN =
  5060.       "\\b(?:" + IC_EVENT_PATTERN + ")[\\s\\S]*=[\\s\\S]*\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b"
  5061.       + "|\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b[\\s\\S]+\\b(?:" + IC_EVENT_PATTERN + ")[\\s\\S]*=";
  5062.       
  5063. var InjectionChecker = {
  5064.   fuzzify: fuzzify,
  5065.   Entities: Entities,
  5066.   syntax: new SyntaxChecker(),
  5067.   _log: function(msg, t, i) {
  5068.     if (msg) msg = this._printable(msg);
  5069.     if(!(i || t)) {
  5070.       msg += " - LINES: ";
  5071.       var lines = [];
  5072.       for (var stack = Components.stack; (stack = stack.caller);) {
  5073.         lines.push(stack.lineNumber);
  5074.       }
  5075.       msg += lines.join(", ");
  5076.     }
  5077.     else {
  5078.       if (t) msg += " - TIME: " + (new Date().getTime() - t);
  5079.       if (i) msg += " - ITER: " + i;
  5080.     }
  5081.     this.dump("[NoScript InjectionChecker] " + msg + "\n");
  5082.   },
  5083.   
  5084.   _printable: function (msg) {
  5085.     return msg.replace(/[^\u0020-\u007e]/g, function(s) { return "{" + s.charCodeAt(0).toString(16) + "}"; });
  5086.   },
  5087.   
  5088.   dump: dump,
  5089.   log: function() {},
  5090.   get logEnabled() { return this.log == this._log; },
  5091.   set logEnabled(v) { this.log = v ? this._log : function() {}; },
  5092.   
  5093.   checkJSSyntax: function(s) {
  5094.     if (/^(?:''|"")?[^\('"]*\)/.test(s)) s = "x(" + s + ")"; // bracket balancing for micro injections like "''),eval(name,''"
  5095.     if (this.syntax.check(s + "/**/")) {
  5096.       this.log("Valid fragment " + s);
  5097.       return true;
  5098.     }
  5099.     return false;
  5100.   },
  5101.   
  5102.   _breakStops: null,
  5103.   get breakStops() {
  5104.     if (this._breakStops) return this._breakStops;
  5105.     var def = "\\/\\?&#;\n\r";
  5106.     var bs = {
  5107.       nq: new RegExp("[" + def + "]")
  5108.     };
  5109.     Array.forEach("'\"", function(c) { bs[c] = new RegExp("[" + def + c + "]"); });
  5110.     return this._breakStops = bs;
  5111.   },
  5112.   
  5113.   reduceBackSlashes: function(bs) {
  5114.     return bs.length % 2 ? "" : "\\";
  5115.   },
  5116.   
  5117.   reduceQuotes: function(s) {
  5118.     if (!/['"]/.test(s) || /\/\*/.test(s)) 
  5119.       return s;
  5120.     // drop noisy backslashes
  5121.     s = s.replace(/\\{2,}/g, this.reduceBackSlashes);
  5122.     
  5123.     // drop escaped quotes
  5124.     s = s.replace(/\\["']/g, "EQ");
  5125.     var expr;
  5126.     for(;;) {
  5127.        expr = s.replace(/(^[^'"\/]*)(["']).*?\2/g, "$1_QS_");
  5128.        if(expr == s) break;
  5129.        s = expr;
  5130.     }
  5131.     return expr;
  5132.   },
  5133.   
  5134.   reduceJSON: function(s) {
  5135.     var m, script, prev;
  5136.     while((m = s.match(/\{[^\{\}]+\}/g))) {
  5137.       prev = s;
  5138.       for each(expr in m) {
  5139.         script = this.reduceQuotes(expr);
  5140.         if (!/[\(=\.]/.test(script) && 
  5141.            this.checkJSSyntax("JSON = " + script) // no-assignment JSON fails with "invalid label"
  5142.         ) { 
  5143.           this.log("Reducing JSON " + expr);
  5144.           s = s.replace(expr, "_JSON_");
  5145.         }
  5146.       }
  5147.       if (s == prev) break;
  5148.     }
  5149.     return s;
  5150.   },
  5151.  
  5152.   _singleAssignmentRx: new RegExp(
  5153.     "\\b(?:" + fuzzify('document|location|setter') + ")\\b" 
  5154.     // + "|/.*/[\\s\\S]*\\b" + fuzzify('source') + "\\b"   // regular expression source extraction
  5155.     + '|' + IC_EVENT_DOS_PATTERN
  5156.   ),
  5157.   _maybeJSRx: new RegExp(
  5158.     '[\\w$\\u0080-\\uFFFF\\]][\\s\\S]*[\\(\\[\\.][\\s\\S]*(?:\\([\\s\\S]*\\)|=)|\\b(?:' +
  5159.     fuzzify('eval|set(?:Timeout|Interval)|[fF]unction|Script') + IC_WINDOW_OPENER_PATTERN +
  5160.     ')\\b[\\s\\S]*\\(|\\b(?:' +
  5161.     fuzzify('setter|location') +
  5162.     ')\\b[\\s\\S]*=|' +
  5163.     IC_EVENT_DOS_PATTERN
  5164.   ),
  5165.   maybeJS: function(expr) {
  5166.     if(/^(?:[^\(\)="']+=[^\(\)='"]+|[\?a-z_0-9;,&=\/]+)$/i.test(expr)) // commonest case, single assignment or simple assignments, no break
  5167.       return this._singleAssignmentRx.test(expr);
  5168.     if (/^(?:[\w\-\.]+\/)*[\w\-\s]+\([\w\-\s]+\)$/.test(expr)) // typical "call like" Wiki URL pattern
  5169.       return /\b(?:eval|set(?:Timeout|Interval)|[F|f]unction|Script|open|alert|confirm|prompt|print|on\w+)\s*\(/.test(expr);
  5170.     
  5171.     return this._maybeJSRx.test(expr); 
  5172.   },
  5173.   checkLastFunction: function() {
  5174.     var expr = this.syntax.lastFunction;
  5175.     expr = expr && this.syntax.lastFunction.toSource().match(/\{([\s\S]*)\}/);
  5176.     return expr && (expr = expr[1]) && 
  5177.       (/=[\s\S]*cookie|\b(?:setter|document|location|\.\W*src)[\s\S]*=|[\[\(]/.test(expr) ||
  5178.       this.maybeJS(expr)
  5179.       );
  5180.   },
  5181.   
  5182.   _createInvalidRanges: function() {
  5183.     function x(n) { return '\\x' + n.toString(16); }
  5184.     
  5185.     var ret = "";
  5186.     var first = -1;
  5187.     var last = -1;
  5188.     var cur = 0x7e;
  5189.     while(cur++ <= 0xff) {
  5190.       try {
  5191.         eval("var _" + String.fromCharCode(cur) + "_=1");
  5192.       } catch(e) {
  5193.         if (!/illegal char/.test(e.message)) continue;
  5194.         if (first == -1) {
  5195.           first = last = cur;
  5196.           ret += x(cur);
  5197.           continue;
  5198.         }
  5199.         if (cur - last == 1) {
  5200.           last = cur;
  5201.           continue;
  5202.         }
  5203.   
  5204.         if(last != first) ret += "-" + x(last);
  5205.         ret+= x(cur);
  5206.         last = first = cur;
  5207.       }
  5208.     }
  5209.     return ret;
  5210.   },
  5211.   get invalidChars() {
  5212.     var rx = new RegExp("^[^\"'/]*[" + this._createInvalidRanges() + "][^\"'/]*$");
  5213.     this.__defineGetter__("invalidChars", function() { return rx; })
  5214.     return rx;
  5215.   },
  5216.   checkJSBreak: function(s) {
  5217.     // Direct script injection breaking JS string literals or comments
  5218.     
  5219.     s = this.reduceJSON(s.replace(/\%\d+[a-z\(]\w*/gi, '`')); // cleanup most urlencoded noise and reduce JSON
  5220.     if (!this.maybeJS(s)) return false;
  5221.     
  5222.     const invalidChars = this.invalidChars;
  5223.     const findInjection = 
  5224.       /(['"\n\r#\]\)]|[\/\?=&](?![\?=&])|\*\/)(?=([\s\S]*?(?:\(|\[[\s\S]*?\]|(?:s\W*e\W*t\W*t\W*e\W*r|l\W*o\W*c\W*a\W*t\W*i\W*o\W*n|\W*o\W*n(?:\W*\w){3,}|\.[@\*\w\$\u0080-\uFFFF])[^&]*=[\s\S]*?[\w\$\u0080-\uFFFF\.\[\]\-]+)))/g;
  5225.     
  5226.     
  5227.     
  5228.     findInjection.lastIndex = 0;
  5229.     var m, breakSeq, subj, expr, lastExpr, quote, len, bs, bsPos, hunt, moved, script, errmsg, pos;
  5230.     
  5231.     const MAX_TIME = 4000, MAX_LOOPS = 400;
  5232.  
  5233.     const t = new Date().getTime();
  5234.     var iterations = 0;
  5235.     
  5236.     while ((m = findInjection.exec(s))) {
  5237.       
  5238.       subj = s.substring(findInjection.lastIndex);
  5239.       if (!this.maybeJS(subj)) {
  5240.          this.log("Fast escape on " + subj, t, iterations);
  5241.          return false;
  5242.       }
  5243.       
  5244.       breakSeq = m[1];
  5245.       expr = subj.match(/^[\s\S]*?[=\)]/);
  5246.       expr = expr && expr[0] || m[2];
  5247.       if (expr.length < m[2].length) expr = m[2];
  5248.       
  5249.       // quickly skip innocuous CGI patterns
  5250.       if ((m = subj.match(/^(?:(?:\.*[\?\w\-\/&:`]+=[\w \-\/:\+%#,`]*(?:[&\|]|$)){2,}|\w+:\/\/\w[\w\-\.]*)/))) {
  5251.         this.log("Skipping CGI pattern in " + subj);
  5252.         findInjection.lastIndex += m[0].length - 1;
  5253.         continue;
  5254.       }
  5255.       
  5256.      
  5257.       
  5258.       quote = breakSeq == '"' || breakSeq == "'" ? breakSeq : '';
  5259.       bs = this.breakStops[quote || 'nq']  
  5260.  
  5261.       len = expr.length;
  5262.       
  5263.       for (moved = false, hunt = !!expr, lastExpr = null; hunt;) {
  5264.         
  5265.         if (new Date().getTime() - t > MAX_TIME) {
  5266.           this.log("Too long execution time! Assuming DOS... " + s, t, iterations);
  5267.           return true;
  5268.         }
  5269.         
  5270.         hunt = expr.length < subj.length;
  5271.         
  5272.         if (moved) {
  5273.           moved = false;
  5274.         } else if (hunt) {
  5275.           bsPos = subj.substring(len).search(bs);
  5276.           if (bsPos < 0) {
  5277.             expr = subj;
  5278.             hunt = false;
  5279.           } else {
  5280.             len += bsPos;
  5281.             if (quote && subj[len] == quote) {
  5282.               len++;
  5283.             }
  5284.             expr = subj.substring(0, len);
  5285.             if (bsPos == 0) len++;
  5286.           }
  5287.         }
  5288.         
  5289.         if(lastExpr == expr) {
  5290.           lastExpr = null;
  5291.           continue;
  5292.         }
  5293.         lastExpr = expr;
  5294.         
  5295.         if(invalidChars.test(expr)) {
  5296.           this.log("Quick skipping invalid chars");
  5297.           continue;
  5298.         }
  5299.         
  5300.         if(quote) {
  5301.           script = this.syntax.unquote(quote + expr, quote);
  5302.           if(script && this.maybeJS(script) &&
  5303.             (this.checkJSSyntax(script) ||
  5304.               /'.+/.test(script) && this.checkJSSyntax("''" + script + "'") ||
  5305.               /".+/.test(script) && this.checkJSSyntax('""' + script + '"')
  5306.             ) && this.checkLastFunction()
  5307.             ) {
  5308.             return true;
  5309.           }
  5310.           script = quote + quote + expr + quote;
  5311.         } else {
  5312.           script = expr;
  5313.         }
  5314.         
  5315.         if (/^(?:[^'"\/\[\(]*[\]\)]|[^"'\/]*(?:`|[^&]&[\w\.]+=[^=]))/
  5316.             .test(script.split("//")[0])) {
  5317.            this.log("SKIP (head syntax) " + script, t, iterations);
  5318.            break; // unrepairable syntax error in the head move left cursor forward 
  5319.         }
  5320.         
  5321.         if (this.maybeJS(this.reduceQuotes(expr))) {
  5322.  
  5323.           if (this.checkJSSyntax(script) && this.checkLastFunction()) {
  5324.             this.log("JS Break Injection detected", t, iterations);
  5325.             return true;
  5326.           }
  5327.           if (++iterations > MAX_LOOPS) {
  5328.             this.log("Too many syntax checks! Assuming DOS... " + s, t, iterations);
  5329.             return true;
  5330.           }
  5331.           if(this.syntax.lastError) { // could be null if we're here thanks to checkLastFunction()
  5332.             errmsg = this.syntax.lastError.message;
  5333.             this.log(errmsg + "\n" + script + "\n---------------", t, iterations);
  5334.             if(!quote) {
  5335.               if (/left-hand/.test(errmsg)) {
  5336.                 m = subj.match(/^([^\]\(\\'"=\?]+?)[\w$\u0080-\uffff\s]+[=\?]/);
  5337.                 if (m) {
  5338.                   findInjection.lastIndex += m[1].length - 1;
  5339.                 }
  5340.                 break;
  5341.               } else if (/unterminated string literal/.test(errmsg)) {
  5342.                 bsPos = subj.substring(len).search(/["']/);
  5343.                 if(bsPos > -1) {
  5344.                   expr = subj.substring(0, len += bsPos + 1);
  5345.                   moved = true;
  5346.                 } else break;
  5347.               } else if (/syntax error/.test(errmsg)) {
  5348.                 bsPos = subj.indexOf("//");
  5349.                 if (bsPos > -1) {
  5350.                   pos = subj.search(/['"\n\\\(]|\/\*/);
  5351.                   if (pos < 0 || pos > bsPos)
  5352.                     break;
  5353.                 }
  5354.               }
  5355.             } else if (/left-hand/.test(errmsg)) break;
  5356.             
  5357.             if (/invalid flag after regular expression|missing ; before statement|invalid label|illegal character/.test(errmsg)) {
  5358.               break; // unrepairable syntax error, move left cursor forward 
  5359.             }
  5360.             if((m = errmsg.match(/\bmissing ([:\]\)\}]) /))) {
  5361.               len = subj.indexOf(m[1], len);
  5362.               if (len > -1) {
  5363.                 expr = subj.substring(0, ++len);
  5364.                 moved = m[1] != ':';
  5365.               } else break;
  5366.             }
  5367.           }
  5368.         }
  5369.       }
  5370.     }
  5371.     this.log(s, t, iterations);
  5372.     return false;
  5373.   },
  5374.   
  5375.   
  5376.   checkJSStunt: function(s) {
  5377.     
  5378.     // simplest navigation act (no dots, no round/square brackets)
  5379.     if (/\bl\W*o\W*c\W*a\W*t\W*i\W*o\W*n\W*(?:\/[\/\*][\s\S]*|\s*)=\W*(?:\/[\/\*][\s\S]*|\s*)n\W*a\W*m\W*e(?:\W+|$)/.test(s)) { 
  5380.       this.log("location = name navigation attempt in " +s);
  5381.       return true;
  5382.     }
  5383.     
  5384.     // check well known and semi-obfuscated -- as in [...]() -- function calls
  5385.     var m = s.match(/\b(?:open|eval|Script|set(?:Timeout|Interval)|[fF]unction|with|\[[^\]]*\w[^\]]*\]|split|replace|toString|substr(?:ing)?|fromCharCode|toLowerCase|unescape|decodeURI(?:Component)?|atob|btoa|\${1,2})\s*(?:\/[\/\*][\s\S]*?)?\([\s\S]*\)/);
  5386.     if (m) {
  5387.       var pos;
  5388.       var js = m[0];
  5389.       if (js.charAt(0) == '[') js = "_xss_" + js;
  5390.       for (;;) {
  5391.         if (this.checkJSSyntax(js)) {
  5392.           return true;
  5393.         }
  5394.         pos = js.lastIndexOf(")");
  5395.         if (pos < 0) break;
  5396.         js = js.substring(0, pos);
  5397.       }
  5398.     }
  5399.     return false;
  5400.   },
  5401.   
  5402.   checkJS: function(s, opts) {
  5403.     this.log(s);
  5404.     // recursive escaping options
  5405.     if (!opts) opts = { uni: true, ent: true };
  5406.     
  5407.     var hasUnicodeEscapes = opts.uni && /\\u[0-9a-f]{4}/.test(s);
  5408.     if (hasUnicodeEscapes && /\\u00(?:22|27|2f)/i.test(s)) {
  5409.       this.log("Unicode-escaped lower ASCII, why would you?");
  5410.       return true;
  5411.     }
  5412.     
  5413.     // the hardcore job!
  5414.     if (this.checkAttributes(s)) return true;
  5415.     if (/[\\=\(]/.test(s) && // quick preliminary screen
  5416.         (this.checkJSStunt(s) || this.checkJSBreak(s)))
  5417.       return true;
  5418.     
  5419.     
  5420.     // recursive cross-unescaping
  5421.     
  5422.     if (hasUnicodeEscapes &&
  5423.         this.checkJS(this.unescapeJS(s), {
  5424.           ent: false, // even if we introduce new entities, they're unrelevant because happen post-spidermonkey
  5425.           uni: false
  5426.         })) 
  5427.       return true;
  5428.     
  5429.     if (opts.ent) {
  5430.       converted = Entities.convertAll(s);
  5431.       if (converted != s && this.checkJS(converted, {
  5432.           ent: false,
  5433.           uni: true // we might have introduced new unicode escapes
  5434.         }))
  5435.         return true;
  5436.     }
  5437.     
  5438.     return false;
  5439.   },
  5440.   
  5441.   unescapeJS: function(s) {
  5442.     return s.replace(/\\u([0-9a-f]{4})/gi, function(s, c) {
  5443.       return String.fromCharCode(parseInt(c, 16));
  5444.     });
  5445.   },
  5446.   
  5447.   unescapeCSS: function(s) {
  5448.     // see http://www.w3.org/TR/CSS21/syndata.html#characters
  5449.     return s.replace(/\\([\da-f]{0,6})\s?/gi, function($0, $1) {
  5450.       try {
  5451.         return String.fromCharCode(parseInt($1, 16));
  5452.       } catch(e) {
  5453.         return "";
  5454.       }
  5455.     });
  5456.   },
  5457.   attributesChecker: new RegExp(
  5458.       "\\W(?:javascript|data):|@" + 
  5459.       ("import\\W*(?:\\/\\*[\\s\\S]*)*(?:[\"']|url[\\s\\S]*\\()" + 
  5460.         "|-moz-binding[\\s\\S]*:[\\s\\S]*url[\\s\\S]*\\(")
  5461.         .replace(/[a-rt-z\-]/g, "\\W*$&"), 
  5462.       "i"),
  5463.   checkAttributes: function(s) {
  5464.     return this.attributesChecker.test(s) ||
  5465.         /\\/.test(s) && this.attributesChecker.test(this.unescapeCSS(s));
  5466.   },
  5467.   
  5468.   HTMLChecker: new RegExp("<\\W*(?:" + 
  5469.    fuzzify("script|form|style|link|object|embed|applet|iframe|frame|base|body|meta|img|svg|video") + 
  5470.     ")|[/'\"]\\W*(?:FSCommand|onerror|on[a-df-z]{3,}[\\s\\x08]*=)", 
  5471.     "i"),
  5472.   checkHTML: function(s) {
  5473.     this.log(s);
  5474.     return this.HTMLChecker.test(s);
  5475.   },
  5476.   
  5477.   base64: false,
  5478.   base64tested: [],
  5479.   get base64Decoder() { return Base64 }, // exposed here just for debugging purposes
  5480.   checkBase64: function(url) {
  5481.     this.log(url);
  5482.     var frags, j, k, l, pos, ff, f;
  5483.     this.base64 = false;
  5484.     // standard base64
  5485.     frags = url.match(/[A-Za-z0-9\+\/]{12,}/g);
  5486.     if (frags) {
  5487.       for (j = 0; j < frags.length; j++) {
  5488.         ff = frags[j].split('/');
  5489.         for (l = ff.length; l > 0; l--)
  5490.           for(k = 0; k < l; k++) {
  5491.             f = ff.slice(k, l).join('/');
  5492.             if (f.length >= 12 && this.checkBase64Frag(f))
  5493.               return true;
  5494.           }
  5495.       }
  5496.     }
  5497.     // URL base64 variant, see http://en.wikipedia.org/wiki/Base64#URL_applications
  5498.     frags = url.match(/[A-Za-z0-9\-_]{12,}/g);
  5499.     if (frags) {
  5500.       for (j = 0; j < frags.length; j++) {
  5501.         f = frags[j].replace(/-/g, '+').replace(/_/, '/');
  5502.         if (this.checkBase64Frag(f)) return true;
  5503.       }
  5504.     }
  5505.     return false;
  5506.   },
  5507.   
  5508.   checkBase64Frag: function(f) {
  5509.     if (this.base64tested.indexOf(f) < 0) {
  5510.       this.base64tested.push(f);
  5511.       try {
  5512.           var s = Base64.decode(f);
  5513.           if(s && s.replace(/[^\w\(\)]/g, '').length > 7 && (this.checkHTML(s) || this.checkJS(s))) {
  5514.             this.log("Detected BASE64 encoded injection: " + f);
  5515.             return this.base64 = true;
  5516.           }
  5517.       } catch(e) {}
  5518.     }
  5519.     return false;
  5520.   },
  5521.   
  5522.   checkURL: function(url) {
  5523.  
  5524.     // iterate escaping until there's nothing more to escape
  5525.  
  5526.     // let's assume protocol and host are safe
  5527.     url = url.replace(/^[a-z]+:\/\/.*?(?=\/|$)/, "");
  5528.     this.base64 = false;
  5529.     this.base64tested = [];
  5530.     
  5531.     return this.checkRecursive(url, 2);
  5532.   },
  5533.   
  5534.   checkRecursive: function(url, depth) {
  5535.     if (typeof(depth) != "number")
  5536.       depth = 2;
  5537.     
  5538.     if (this.checkHTML(url) || this.checkJS(url) || this.checkBase64(url))
  5539.       return true;
  5540.     
  5541.     if (--depth <= 0)
  5542.       return false;
  5543.     
  5544.     if (/\+/.test(url) && this.checkRecursive(this.urlUnescape(url.replace(/\+/g, ' '), depth)))
  5545.       return true;
  5546.     
  5547.     var unescaped = this.urlUnescape(url);
  5548.     if (unescaped != url && this.checkRecursive(unescaped, depth))
  5549.       return true;
  5550.     
  5551.     url = this.ebayUnescape(unescaped);
  5552.     if (url != unescaped && this.checkRecursive(url, depth))
  5553.       return true;
  5554.     
  5555.     return false;
  5556.   },
  5557.   
  5558.   urlUnescape: function(url) {
  5559.     try {
  5560.       return decodeURIComponent(url);
  5561.     } catch(warn) {
  5562.       this.log("Problem decoding " + url + ", maybe not an UTF-8 encoding? " + warn.message);
  5563.       return unescape(url);
  5564.     }
  5565.   },
  5566.   
  5567.   ebayUnescape: function(url) {
  5568.     return url.replace(/Q([\da-fA-F]{2})/g, function(s, c) {
  5569.       return String.fromCharCode(parseInt(c, 16));
  5570.     });
  5571.   }
  5572. };
  5573.  
  5574.  
  5575.  
  5576. function XSanitizer(primaryBlacklist, extraBlacklist) {
  5577.   this.primaryBlacklist = primaryBlacklist;
  5578.   this.extraBlacklist = extraBlacklist;
  5579.   this.injectionChecker = InjectionChecker;
  5580. }
  5581.  
  5582. XSanitizer.prototype = {
  5583.   brutal: false,
  5584.   base64: false,
  5585.   sanitizeURL: function(url) {
  5586.     var original = url.clone();
  5587.     this.brutal = this.brutal || this.injectionChecker.checkURL(url.spec);
  5588.     this.base64 = this.injectionChecker.base64;
  5589.     
  5590.     const changes = { minor: false, major: false, qs: false };
  5591.     // sanitize credentials
  5592.     if (url.username) url.username = this.sanitizeEnc(url.username);
  5593.     if (url.password) url.password = this.sanitizeEnc(url.password);
  5594.     url.host = this.sanitizeEnc(url.host);
  5595.     
  5596.     if (url instanceof CI.nsIURL) {
  5597.       // sanitize path
  5598.      
  5599.       if (url.param) {
  5600.         url.path = this.sanitizeURIComponent(url.path); // param is the URL part after filePath and a semicolon ?!
  5601.       } else if(url.filePath) { 
  5602.         url.filePath = this.sanitizeURIComponent(url.filePath); // true == lenient == allow ()=
  5603.       }
  5604.       // sanitize query
  5605.       if (url.query) {
  5606.         url.query = this.sanitizeQuery(url.query, changes);
  5607.         if (this.brutal) {
  5608.           url.query = this.sanitizeWholeQuery(url.query, changes);
  5609.         }
  5610.       }
  5611.       // sanitize fragment
  5612.       var fragPos = url.path.indexOf("#");
  5613.       if (url.ref || fragPos > -1) {
  5614.         if (fragPos >= url.filePath.length + url.query.length) {
  5615.           url.path = url.path.substring(0, fragPos) + "#" + this.sanitizeEnc(url.path.substring(fragPos + 1));
  5616.         } else {
  5617.           url.ref = this.sanitizeEnc(url.ref);
  5618.         }
  5619.       }
  5620.     } else {
  5621.       // fallback for non-URL URIs, we should never get here anyway
  5622.       if (url.path) url.path = this.sanitizeURIComponent(url.Path);
  5623.     }
  5624.     
  5625.     var urlSpec = url.spec;
  5626.     var neutralized = Entities.neutralizeAll(urlSpec, /[^\\'"\x00-\x07\x09\x0B\x0C\x0E-\x1F\x7F<>]/);
  5627.     if (urlSpec != neutralized) url.spec = neutralized;
  5628.     
  5629.     if (this.base64) {
  5630.       url.spec = url.prePath; // drastic, but with base64 we cannot take the risk!
  5631.     }
  5632.     
  5633.     if (url.getRelativeSpec(original) && unescape(url.spec) != unescape(original.spec)) { // ok, this seems overkill but take my word, the double check is needed
  5634.       changes.minor = true;
  5635.       changes.major = changes.major || changes.qs || 
  5636.                       unescape(original.spec.replace(/\?.*/g, "")) 
  5637.                         != unescape(url.spec.replace(/\?.*/g, ""));
  5638.       url.spec = url.spec.replace(/'/g, "%27")
  5639.       if (changes.major) {
  5640.         url.ref = Math.random().toString().concat(Math.round(Math.random() * 999 + 1)).replace(/0./, '') // randomize URI
  5641.       }
  5642.     } else {
  5643.       changes.minor = false;
  5644.       url.spec = original.spec.replace(/'/g, "%27");
  5645.     }
  5646.     return changes;
  5647.   },
  5648.   
  5649.   sanitizeWholeQuery: function(query, changes) {
  5650.     var original = query;
  5651.     query = Entities.convertAll(query);
  5652.     if (query == original) return query;
  5653.     var unescaped = unescape(original);
  5654.     query = this.sanitize(unescaped);
  5655.     if (query == unescaped) return original;
  5656.     if(changes) changes.qs = true;
  5657.     return escape(query);
  5658.   },
  5659.   
  5660.   sanitizeQuery: function(query, changes, sep) {
  5661.     // replace every character matching noscript.filterXGetRx with a single ASCII space (0x20)
  5662.     changes = changes || {};
  5663.     
  5664.     if (!sep) {
  5665.       sep = query.indexOf("&") > -1 ? "&" : ";" 
  5666.     }
  5667.     const parms = query.split(sep);
  5668.     var j, pieces, k, pz, origPz, encodedPz, nestedURI, qpos, apos;
  5669.     
  5670.     for (j = parms.length; j-- > 0;) {
  5671.       pieces = parms[j].split("=");
  5672.       try {
  5673.         for (k = pieces.length; k-- > 0;) {
  5674.           origPz = pz = decodeURIComponent(encodedPz = pieces[k]);
  5675.           nestedURI = null;
  5676.           if (/^https?:\/\//i.test(pz)) {
  5677.             // try to sanitize as a nested URL
  5678.             try {
  5679.               nestedURI = SiteUtils.ios.newURI(pz, null, null).QueryInterface(CI.nsIURL);
  5680.               changes.qs = changes.qs || this.sanitizeURL(nestedURI).major;
  5681.               pz = nestedURI.spec;
  5682.             } catch(e) {
  5683.               nestedURI = null;
  5684.             }
  5685.           }
  5686.           
  5687.           if (!nestedURI) {
  5688.             qpos = pz.indexOf("?");
  5689.             spos = pz.search(/[&;]/);
  5690.             if (qpos > -1 && spos > qpos) { 
  5691.               // recursive query string?
  5692.               if (qpos > -1 && spos > qpos) {
  5693.                 // recursively sanitize it as a whole qs
  5694.                 pz = this.sanitizeQuery(pz, changes);
  5695.               } else {
  5696.                 // split, sanitize and rejoin
  5697.                 pz = [ this.sanitize(pz.substring(0, qpos)), 
  5698.                        this.sanitizeQuery(pz.substring(qpos + 1), changes)
  5699.                      ].join("?")
  5700.               }
  5701.             } else {
  5702.               pz = this.sanitize(pz);
  5703.             }
  5704.             if (origPz != pz) changes.qs = true;
  5705.           }
  5706.           
  5707.           pieces[k] = encodedPz.indexOf("+") > - 1 ? escape(pz) : encodeURIComponent(pz);
  5708.         }
  5709.         parms[j] = pieces.join("=");
  5710.       } catch(e) { 
  5711.         // decoding exception, skip this param
  5712.         parms.splice(j, 1);
  5713.       }
  5714.     }
  5715.     return parms.join(sep);
  5716.   },
  5717.   
  5718.   sanitizeURIComponent: function(s) {
  5719.     try {
  5720.       return encodeURI(this.sanitize(decodeURIComponent(s)));
  5721.     } catch(e) {
  5722.       return "";
  5723.     }
  5724.   },
  5725.   sanitizeEnc: function(s) {
  5726.     try {
  5727.       return encodeURIComponent(this.sanitize(decodeURIComponent(s)));
  5728.     } catch(e) {
  5729.       return "";
  5730.     }
  5731.   },
  5732.   sanitize: function(unsanitized) {
  5733.     // deeply convert entities
  5734.     var s, orig;
  5735.     orig = s = Entities.convertDeep(unsanitized);
  5736.     
  5737.     if (s.indexOf('"') > -1 && !this.brutal) {
  5738.       // try to play nice on search engine queries with grouped quoted elements
  5739.       // by allowing double quotes but stripping even more aggressively other chars
  5740.       
  5741.       // Google preserves "$" and recognizes ~, + and ".." as operators
  5742.       // All the other non alphanumeric chars (aside double quotes) are ignored.
  5743.       // We will preserve the site: modifier as well
  5744.       // Ref.: http://www.google.com/help/refinesearch.html
  5745.       s = s.replace(/[^\w\$\+\.\~"&;\- :\u0080-\uffff]/g, 
  5746.           " " // strip everything but alphnum and operators
  5747.           ).replace(":", 
  5748.           function(k, pos, s) { // strip colons as well, unless it's the site: operator
  5749.             return (s.substring(0, pos) == "site" || s.substring(pos - 5) == " site") ? ":" : " " 
  5750.           }
  5751.         );
  5752.       if (s.replace(/[^"]/g, "").length % 2) s += '"'; // close unpaired quotes
  5753.       return s;
  5754.     }
  5755.     // regular duty
  5756.     s = s.replace(this.primaryBlacklist, " ");
  5757.     
  5758.     s = s.replace(/javascript\s*:+|data\s*:+|-moz-binding|@import/ig, function(m) { return m.replace(/\W/g, " "); });
  5759.     
  5760.     if (this.extraBlacklist) { // additional user-defined blacklist for emergencies
  5761.       s = s.replace(this.extraBlacklist, " "); 
  5762.     }
  5763.     
  5764.     if (this.brutal) { // injection checks were positive
  5765.       s = s.replace(/['\(\)\=\[\]]/g, " ")
  5766.            .replace(this._brutalReplRx, String.toUpperCase)
  5767.            .replace(/Q[\da-fA-Fa]{2}/g, "Q20"); // Ebay-style escaping
  5768.     }
  5769.     
  5770.     return s == orig ? unsanitized : s;
  5771.   },
  5772.   
  5773.   _regularReplRx: new RegExp(
  5774.     fuzzify('(?:javascript|data)') + '\\W*:+|' +
  5775.       fuzzify('-moz-binding|@import'), 
  5776.     "ig"
  5777.   ),
  5778.   _brutalReplRx: new RegExp(
  5779.     '(?:' + fuzzify('setter|location|cookie|name|document|') +
  5780.     IC_WINDOW_OPENER_PATTERN + '|' + IC_EVENT_PATTERN + ')',
  5781.     "g"
  5782.   )
  5783.   
  5784. };
  5785.  
  5786. // we need this because of https://bugzilla.mozilla.org/show_bug.cgi?id=439276
  5787.  
  5788. var Base64 = {
  5789.  
  5790.   decode : function (input) {
  5791.     var output = '';
  5792.     var chr1, chr2, chr3;
  5793.     var enc1, enc2, enc3, enc4;
  5794.     var i = 0;
  5795.     
  5796.     // if (/[^A-Za-z0-9\+\/\=]/.test(input)) return ""; // we don't need this, caller checks for us
  5797.  
  5798.     const k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  5799.     while (i < input.length) {
  5800.  
  5801.         enc1 = k.indexOf(input.charAt(i++));
  5802.         enc2 = k.indexOf(input.charAt(i++));
  5803.         enc3 = k.indexOf(input.charAt(i++));
  5804.         enc4 = k.indexOf(input.charAt(i++));
  5805.  
  5806.         chr1 = (enc1 << 2) | (enc2 >> 4);
  5807.         chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  5808.         chr3 = ((enc3 & 3) << 6) | enc4;
  5809.  
  5810.         output += String.fromCharCode(chr1);
  5811.  
  5812.         if (enc3 != 64) {
  5813.           output += String.fromCharCode(chr2);
  5814.         }
  5815.         if (enc4 != 64) {
  5816.           output += String.fromCharCode(chr3);
  5817.         }
  5818.  
  5819.     }
  5820.     return output;
  5821.  
  5822.   }
  5823. }
  5824.  
  5825.  
  5826.